You've already forked hotpocket
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 98e5e1891a | |||
| 06343e6ed3 | |||
| 82a3b612ec | |||
| 1e549d3fc2 | |||
| 55126f4af6 | |||
| cca49f2292 | |||
| 23f8296659 | |||
| 9abed01e53 | |||
| 3b1aba9672 | |||
| 22061486a8 | |||
| 38785ccf92 |
31
README.md
31
README.md
@@ -1,6 +1,23 @@
|
|||||||
# HotPocket by BTHLabs
|
# HotPocket by BTHLabs
|
||||||
|
|
||||||
This repository contains the _HotPocket_ project.
|
Minimal self-hosted bookmarking app :).
|
||||||
|
|
||||||
|
## The what, the why and the ugly
|
||||||
|
|
||||||
|
HotPocket is a minimal self-hosted bookmarking app. It combines a Web
|
||||||
|
application, companion apps, browser extensions to give you a way to quickly
|
||||||
|
save links for later.
|
||||||
|
|
||||||
|
HotPocket came to be to fill in the blank left by Pocket, after Mozilla shut it
|
||||||
|
down. I looked at the existing alternatives and found them either too
|
||||||
|
feature-rich, too involved to self-host or otherwise not to my liking. So I
|
||||||
|
decided to sit down and build something for myself.
|
||||||
|
|
||||||
|
With the what and why out of the way, let's talk about the ugly... At its core
|
||||||
|
HotPocket is a personal project. I built it by myself and for myself. It may
|
||||||
|
or may not fit your needs. If it does, happy saving!
|
||||||
|
|
||||||
|
If you're feeling up for an adventure, continue reading below :).
|
||||||
|
|
||||||
## Development setup
|
## Development setup
|
||||||
|
|
||||||
@@ -66,7 +83,7 @@ $ docker run --rm -it \
|
|||||||
-e HOTPOCKET_BACKEND_INITIAL_ACCOUNT_USERNAME=hotpocket \
|
-e HOTPOCKET_BACKEND_INITIAL_ACCOUNT_USERNAME=hotpocket \
|
||||||
-e HOTPOCKET_BACKEND_INITIAL_ACCOUNT_PASSWORD=hotpocketm4st3r \
|
-e HOTPOCKET_BACKEND_INITIAL_ACCOUNT_PASSWORD=hotpocketm4st3r \
|
||||||
-p 8000:8000 \
|
-p 8000:8000 \
|
||||||
hotpocket/backend:aio-v25.11.18-01
|
hotpocket/backend:aio-v25.11.26-01
|
||||||
```
|
```
|
||||||
|
|
||||||
The command above will set up and start the application. The SQLite file will
|
The command above will set up and start the application. The SQLite file will
|
||||||
@@ -128,6 +145,7 @@ that can be used to configure the services.
|
|||||||
| `HOTPOCKET_BACKEND_RUN_MIGRATIONS` | `false` or `true` | Set to `true` to run database muigrations when the container starts. |
|
| `HOTPOCKET_BACKEND_RUN_MIGRATIONS` | `false` or `true` | Set to `true` to run database muigrations when the container starts. |
|
||||||
| `HOTPOCKET_BACKEND_INITIAL_ACCOUNT_USERNAME` | N/A | Username for the initial account. |
|
| `HOTPOCKET_BACKEND_INITIAL_ACCOUNT_USERNAME` | N/A | Username for the initial account. |
|
||||||
| `HOTPOCKET_BACKEND_INITIAL_ACCOUNT_PASSWORD` | N/A | Password for the initial account. |
|
| `HOTPOCKET_BACKEND_INITIAL_ACCOUNT_PASSWORD` | N/A | Password for the initial account. |
|
||||||
|
| `HOTPOCKET_BACKEND_OPERATOR_EMAIL` | N/A | Instance operator's e-mail. Used to display extra language on login page. |
|
||||||
|
|
||||||
**Env and App settings**
|
**Env and App settings**
|
||||||
|
|
||||||
@@ -158,6 +176,15 @@ method's name in the UI and defaults to `OIDC`.
|
|||||||
|
|
||||||
**NOTE:** Currently, only Keycloak has been tested with this login method.
|
**NOTE:** Currently, only Keycloak has been tested with this login method.
|
||||||
|
|
||||||
|
### Volumes
|
||||||
|
|
||||||
|
Both images declare `/srv/run` to be a volume. It's intended to keep the
|
||||||
|
service's runtime data, including but not limited to PID files, UNIX sockets
|
||||||
|
etc. It's recommended to persist this volume.
|
||||||
|
|
||||||
|
Additionally, the `deployment` image declares `/srv/uploads` to be a volume.
|
||||||
|
It's recommeded to persist this volume.
|
||||||
|
|
||||||
## Author
|
## Author
|
||||||
|
|
||||||
_HotPocket_ is developed by [BTHLabs](https://www.bthlabs.pl/).
|
_HotPocket_ is developed by [BTHLabs](https://www.bthlabs.pl/).
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
backend:
|
backend:
|
||||||
image: "hotpocket/backend:aio-v25.11.18-01"
|
image: "bthlabs/hotpocket:aio-v25.11.26-01"
|
||||||
environment:
|
environment:
|
||||||
HOTPOCKET_BACKEND_SECRET_KEY: "thisisntright"
|
HOTPOCKET_BACKEND_SECRET_KEY: "thisisntright"
|
||||||
HOTPOCKET_BACKEND_INITIAL_ACCOUNT_USERNAME: "hotpocket"
|
HOTPOCKET_BACKEND_INITIAL_ACCOUNT_USERNAME: "hotpocket"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ x-backend-environment: &x-backend-environment
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
webapp:
|
webapp:
|
||||||
image: "hotpocket/backend:deployment-v25.11.18-01"
|
image: "bthlabs/hotpocket:deployment-v25.11.26-01"
|
||||||
environment:
|
environment:
|
||||||
<<: *x-backend-environment
|
<<: *x-backend-environment
|
||||||
HOTPOCKET_BACKEND_ALLOWED_HOSTS: "app.staging.hotpocket.bthlab.bthlabs.net"
|
HOTPOCKET_BACKEND_ALLOWED_HOSTS: "app.staging.hotpocket.bthlab.bthlabs.net"
|
||||||
@@ -21,7 +21,7 @@ services:
|
|||||||
restart: "unless-stopped"
|
restart: "unless-stopped"
|
||||||
|
|
||||||
admin:
|
admin:
|
||||||
image: "hotpocket/backend:deployment-v25.11.18-01"
|
image: "bthlabs/hotpocket:deployment-v25.11.26-01"
|
||||||
environment:
|
environment:
|
||||||
<<: *x-backend-environment
|
<<: *x-backend-environment
|
||||||
HOTPOCKET_BACKEND_APP: "admin"
|
HOTPOCKET_BACKEND_APP: "admin"
|
||||||
@@ -35,7 +35,7 @@ services:
|
|||||||
restart: "unless-stopped"
|
restart: "unless-stopped"
|
||||||
|
|
||||||
celery-worker:
|
celery-worker:
|
||||||
image: "hotpocket/backend:deployment-v25.11.18-01"
|
image: "bthlabs/hotpocket:deployment-v25.11.26-01"
|
||||||
command:
|
command:
|
||||||
- "/srv/venv/bin/celery"
|
- "/srv/venv/bin/celery"
|
||||||
- "-A"
|
- "-A"
|
||||||
@@ -57,7 +57,7 @@ services:
|
|||||||
restart: "unless-stopped"
|
restart: "unless-stopped"
|
||||||
|
|
||||||
celery-beat:
|
celery-beat:
|
||||||
image: "hotpocket/backend:deployment-v25.11.18-01"
|
image: "bthlabs/hotpocket:deployment-v25.11.26-01"
|
||||||
command:
|
command:
|
||||||
- "/srv/venv/bin/celery"
|
- "/srv/venv/bin/celery"
|
||||||
- "-A"
|
- "-A"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "hotpocket-workspace"
|
name = "hotpocket-workspace"
|
||||||
version = "25.11.18"
|
version = "25.11.26"
|
||||||
description = "HotPocket Workspace"
|
description = "HotPocket Workspace"
|
||||||
authors = ["Tomek Wójcik <contact@bthlabs.pl>"]
|
authors = ["Tomek Wójcik <contact@bthlabs.pl>"]
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
4C1159202E8B055F003B34AD /* Save to HotPocket Development.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 4CBCEA4F2E81CB9500722009 /* Save to HotPocket Development.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
4C1159202E8B055F003B34AD /* Save to HotPocket.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 4CBCEA4F2E81CB9500722009 /* Save to HotPocket.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
4C2F0C692E851BBD0033F5C2 /* Save to HotPocket Development.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 4C2F0C5E2E851BBD0033F5C2 /* Save to HotPocket Development.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
4C2F0C692E851BBD0033F5C2 /* Save to HotPocket.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 4C2F0C5E2E851BBD0033F5C2 /* Save to HotPocket.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
4C70F30D2E8869FB00320048 /* HPShareExtensionHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C70F30C2E8869FB00320048 /* HPShareExtensionHelper.m */; };
|
4C70F30D2E8869FB00320048 /* HPShareExtensionHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C70F30C2E8869FB00320048 /* HPShareExtensionHelper.m */; };
|
||||||
4C70F30E2E8869FB00320048 /* HPShareExtensionHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C70F30C2E8869FB00320048 /* HPShareExtensionHelper.m */; };
|
4C70F30E2E8869FB00320048 /* HPShareExtensionHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C70F30C2E8869FB00320048 /* HPShareExtensionHelper.m */; };
|
||||||
4C70F3152E886A8F00320048 /* HPSharedItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C70F3142E886A8F00320048 /* HPSharedItem.m */; };
|
4C70F3152E886A8F00320048 /* HPSharedItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C70F3142E886A8F00320048 /* HPSharedItem.m */; };
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
dstSubfolderSpec = 13;
|
dstSubfolderSpec = 13;
|
||||||
files = (
|
files = (
|
||||||
4CABCAD62E56F0C900D8A354 /* HotPocket Extension.appex in Embed Foundation Extensions */,
|
4CABCAD62E56F0C900D8A354 /* HotPocket Extension.appex in Embed Foundation Extensions */,
|
||||||
4C2F0C692E851BBD0033F5C2 /* Save to HotPocket Development.appex in Embed Foundation Extensions */,
|
4C2F0C692E851BBD0033F5C2 /* Save to HotPocket.appex in Embed Foundation Extensions */,
|
||||||
);
|
);
|
||||||
name = "Embed Foundation Extensions";
|
name = "Embed Foundation Extensions";
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
dstPath = "";
|
dstPath = "";
|
||||||
dstSubfolderSpec = 13;
|
dstSubfolderSpec = 13;
|
||||||
files = (
|
files = (
|
||||||
4C1159202E8B055F003B34AD /* Save to HotPocket Development.appex in Embed Foundation Extensions */,
|
4C1159202E8B055F003B34AD /* Save to HotPocket.appex in Embed Foundation Extensions */,
|
||||||
4CABCAE02E56F0C900D8A354 /* HotPocket Extension.appex in Embed Foundation Extensions */,
|
4CABCAE02E56F0C900D8A354 /* HotPocket Extension.appex in Embed Foundation Extensions */,
|
||||||
);
|
);
|
||||||
name = "Embed Foundation Extensions";
|
name = "Embed Foundation Extensions";
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
4C2F0C5E2E851BBD0033F5C2 /* Save to HotPocket Development.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Save to HotPocket Development.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
4C2F0C5E2E851BBD0033F5C2 /* Save to HotPocket.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Save to HotPocket.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
4C70F30B2E8869FB00320048 /* HPShareExtensionHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HPShareExtensionHelper.h; sourceTree = "<group>"; };
|
4C70F30B2E8869FB00320048 /* HPShareExtensionHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HPShareExtensionHelper.h; sourceTree = "<group>"; };
|
||||||
4C70F30C2E8869FB00320048 /* HPShareExtensionHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HPShareExtensionHelper.m; sourceTree = "<group>"; };
|
4C70F30C2E8869FB00320048 /* HPShareExtensionHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HPShareExtensionHelper.m; sourceTree = "<group>"; };
|
||||||
4C70F3132E886A8F00320048 /* HPSharedItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HPSharedItem.h; sourceTree = "<group>"; };
|
4C70F3132E886A8F00320048 /* HPSharedItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HPSharedItem.h; sourceTree = "<group>"; };
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
4CABCAC62E56F0C900D8A354 /* HotPocket Development.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "HotPocket Development.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
4CABCAC62E56F0C900D8A354 /* HotPocket Development.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "HotPocket Development.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; };
|
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; };
|
4CABCADF2E56F0C900D8A354 /* HotPocket Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "HotPocket Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
4CBCEA4F2E81CB9500722009 /* Save to HotPocket Development.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Save to HotPocket Development.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
4CBCEA4F2E81CB9500722009 /* Save to HotPocket.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Save to HotPocket.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
@@ -393,8 +393,8 @@
|
|||||||
4CABCAC62E56F0C900D8A354 /* HotPocket Development.app */,
|
4CABCAC62E56F0C900D8A354 /* HotPocket Development.app */,
|
||||||
4CABCAD52E56F0C900D8A354 /* HotPocket Extension.appex */,
|
4CABCAD52E56F0C900D8A354 /* HotPocket Extension.appex */,
|
||||||
4CABCADF2E56F0C900D8A354 /* HotPocket Extension.appex */,
|
4CABCADF2E56F0C900D8A354 /* HotPocket Extension.appex */,
|
||||||
4CBCEA4F2E81CB9500722009 /* Save to HotPocket Development.appex */,
|
4CBCEA4F2E81CB9500722009 /* Save to HotPocket.appex */,
|
||||||
4C2F0C5E2E851BBD0033F5C2 /* Save to HotPocket Development.appex */,
|
4C2F0C5E2E851BBD0033F5C2 /* Save to HotPocket.appex */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -421,7 +421,7 @@
|
|||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
);
|
);
|
||||||
productName = "iOS (Share Extension)";
|
productName = "iOS (Share Extension)";
|
||||||
productReference = 4C2F0C5E2E851BBD0033F5C2 /* Save to HotPocket Development.appex */;
|
productReference = 4C2F0C5E2E851BBD0033F5C2 /* Save to HotPocket.appex */;
|
||||||
productType = "com.apple.product-type.app-extension";
|
productType = "com.apple.product-type.app-extension";
|
||||||
};
|
};
|
||||||
4CABCAAF2E56F0C900D8A354 /* HotPocket (iOS) */ = {
|
4CABCAAF2E56F0C900D8A354 /* HotPocket (iOS) */ = {
|
||||||
@@ -537,7 +537,7 @@
|
|||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
);
|
);
|
||||||
productName = "macOS (Share Extension)";
|
productName = "macOS (Share Extension)";
|
||||||
productReference = 4CBCEA4F2E81CB9500722009 /* Save to HotPocket Development.appex */;
|
productReference = 4CBCEA4F2E81CB9500722009 /* Save to HotPocket.appex */;
|
||||||
productType = "com.apple.product-type.app-extension";
|
productType = "com.apple.product-type.app-extension";
|
||||||
};
|
};
|
||||||
/* End PBXNativeTarget section */
|
/* End PBXNativeTarget section */
|
||||||
@@ -718,7 +718,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_ENTITLEMENTS = "iOS (Share Extension)/iOS (Share Extension).entitlements";
|
CODE_SIGN_ENTITLEMENTS = "iOS (Share Extension)/iOS (Share Extension).entitlements";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 2025111801;
|
CURRENT_PROJECT_VERSION = 2025112601;
|
||||||
DEVELOPMENT_TEAM = 648728X64K;
|
DEVELOPMENT_TEAM = 648728X64K;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "iOS (Share Extension)/Info.plist";
|
INFOPLIST_FILE = "iOS (Share Extension)/Info.plist";
|
||||||
@@ -731,9 +731,9 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 25.11.18;
|
MARKETING_VERSION = 25.11.26;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.ShareExtension;
|
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.ShareExtension;
|
||||||
PRODUCT_NAME = "Save to HotPocket Development";
|
PRODUCT_NAME = "Save to HotPocket";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
@@ -751,7 +751,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_ENTITLEMENTS = "iOS (Share Extension)/iOS (Share Extension).entitlements";
|
CODE_SIGN_ENTITLEMENTS = "iOS (Share Extension)/iOS (Share Extension).entitlements";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 2025111801;
|
CURRENT_PROJECT_VERSION = 2025112601;
|
||||||
DEVELOPMENT_TEAM = 648728X64K;
|
DEVELOPMENT_TEAM = 648728X64K;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "iOS (Share Extension)/Info.plist";
|
INFOPLIST_FILE = "iOS (Share Extension)/Info.plist";
|
||||||
@@ -764,7 +764,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 25.11.18;
|
MARKETING_VERSION = 25.11.26;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.ShareExtension;
|
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.ShareExtension;
|
||||||
PRODUCT_NAME = "Save to HotPocket";
|
PRODUCT_NAME = "Save to HotPocket";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -784,7 +784,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 2025111801;
|
CURRENT_PROJECT_VERSION = 2025112601;
|
||||||
DEVELOPMENT_TEAM = 648728X64K;
|
DEVELOPMENT_TEAM = 648728X64K;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "iOS (Extension)/Info.plist";
|
INFOPLIST_FILE = "iOS (Extension)/Info.plist";
|
||||||
@@ -797,7 +797,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 25.11.18;
|
MARKETING_VERSION = 25.11.26;
|
||||||
OTHER_LDFLAGS = (
|
OTHER_LDFLAGS = (
|
||||||
"-framework",
|
"-framework",
|
||||||
SafariServices,
|
SafariServices,
|
||||||
@@ -819,7 +819,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 2025111801;
|
CURRENT_PROJECT_VERSION = 2025112601;
|
||||||
DEVELOPMENT_TEAM = 648728X64K;
|
DEVELOPMENT_TEAM = 648728X64K;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "iOS (Extension)/Info.plist";
|
INFOPLIST_FILE = "iOS (Extension)/Info.plist";
|
||||||
@@ -832,7 +832,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 25.11.18;
|
MARKETING_VERSION = 25.11.26;
|
||||||
OTHER_LDFLAGS = (
|
OTHER_LDFLAGS = (
|
||||||
"-framework",
|
"-framework",
|
||||||
SafariServices,
|
SafariServices,
|
||||||
@@ -858,7 +858,7 @@
|
|||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_ENTITLEMENTS = "iOS (App)/HotPocket (iOS).entitlements";
|
CODE_SIGN_ENTITLEMENTS = "iOS (App)/HotPocket (iOS).entitlements";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 2025111801;
|
CURRENT_PROJECT_VERSION = 2025112601;
|
||||||
DEVELOPMENT_TEAM = 648728X64K;
|
DEVELOPMENT_TEAM = 648728X64K;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "iOS (App)/Info.plist";
|
INFOPLIST_FILE = "iOS (App)/Info.plist";
|
||||||
@@ -878,7 +878,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 25.11.18;
|
MARKETING_VERSION = 25.11.26;
|
||||||
OTHER_LDFLAGS = (
|
OTHER_LDFLAGS = (
|
||||||
"-framework",
|
"-framework",
|
||||||
SafariServices,
|
SafariServices,
|
||||||
@@ -904,7 +904,7 @@
|
|||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_ENTITLEMENTS = "iOS (App)/HotPocket (iOS).entitlements";
|
CODE_SIGN_ENTITLEMENTS = "iOS (App)/HotPocket (iOS).entitlements";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 2025111801;
|
CURRENT_PROJECT_VERSION = 2025112601;
|
||||||
DEVELOPMENT_TEAM = 648728X64K;
|
DEVELOPMENT_TEAM = 648728X64K;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "iOS (App)/Info.plist";
|
INFOPLIST_FILE = "iOS (App)/Info.plist";
|
||||||
@@ -924,7 +924,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 25.11.18;
|
MARKETING_VERSION = 25.11.26;
|
||||||
OTHER_LDFLAGS = (
|
OTHER_LDFLAGS = (
|
||||||
"-framework",
|
"-framework",
|
||||||
SafariServices,
|
SafariServices,
|
||||||
@@ -950,7 +950,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = "macOS (Extension)/HotPocket.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "macOS (Extension)/HotPocket.entitlements";
|
||||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 2025111801;
|
CURRENT_PROJECT_VERSION = 2025112601;
|
||||||
DEVELOPMENT_TEAM = 648728X64K;
|
DEVELOPMENT_TEAM = 648728X64K;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
@@ -965,7 +965,7 @@
|
|||||||
"@executable_path/../../../../Frameworks",
|
"@executable_path/../../../../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
||||||
MARKETING_VERSION = 25.11.18;
|
MARKETING_VERSION = 25.11.26;
|
||||||
OTHER_LDFLAGS = (
|
OTHER_LDFLAGS = (
|
||||||
"-framework",
|
"-framework",
|
||||||
SafariServices,
|
SafariServices,
|
||||||
@@ -985,7 +985,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = "macOS (Extension)/HotPocket.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "macOS (Extension)/HotPocket.entitlements";
|
||||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 2025111801;
|
CURRENT_PROJECT_VERSION = 2025112601;
|
||||||
DEVELOPMENT_TEAM = 648728X64K;
|
DEVELOPMENT_TEAM = 648728X64K;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
@@ -1000,7 +1000,7 @@
|
|||||||
"@executable_path/../../../../Frameworks",
|
"@executable_path/../../../../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
||||||
MARKETING_VERSION = 25.11.18;
|
MARKETING_VERSION = 25.11.26;
|
||||||
OTHER_LDFLAGS = (
|
OTHER_LDFLAGS = (
|
||||||
"-framework",
|
"-framework",
|
||||||
SafariServices,
|
SafariServices,
|
||||||
@@ -1022,7 +1022,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = "macOS (App)/HotPocket.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "macOS (App)/HotPocket.entitlements";
|
||||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 2025111801;
|
CURRENT_PROJECT_VERSION = 2025112601;
|
||||||
DEVELOPMENT_TEAM = 648728X64K;
|
DEVELOPMENT_TEAM = 648728X64K;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -1038,7 +1038,7 @@
|
|||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
||||||
MARKETING_VERSION = 25.11.18;
|
MARKETING_VERSION = 25.11.26;
|
||||||
OTHER_LDFLAGS = (
|
OTHER_LDFLAGS = (
|
||||||
"-framework",
|
"-framework",
|
||||||
SafariServices,
|
SafariServices,
|
||||||
@@ -1061,7 +1061,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = "macOS (App)/HotPocket.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "macOS (App)/HotPocket.entitlements";
|
||||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 2025111801;
|
CURRENT_PROJECT_VERSION = 2025112601;
|
||||||
DEVELOPMENT_TEAM = 648728X64K;
|
DEVELOPMENT_TEAM = 648728X64K;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -1077,7 +1077,7 @@
|
|||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
||||||
MARKETING_VERSION = 25.11.18;
|
MARKETING_VERSION = 25.11.26;
|
||||||
OTHER_LDFLAGS = (
|
OTHER_LDFLAGS = (
|
||||||
"-framework",
|
"-framework",
|
||||||
SafariServices,
|
SafariServices,
|
||||||
@@ -1211,7 +1211,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = "macOS (Share Extension)/macOS (Share Extension).entitlements";
|
CODE_SIGN_ENTITLEMENTS = "macOS (Share Extension)/macOS (Share Extension).entitlements";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 2025111801;
|
CURRENT_PROJECT_VERSION = 2025112601;
|
||||||
DEVELOPMENT_TEAM = 648728X64K;
|
DEVELOPMENT_TEAM = 648728X64K;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -1225,9 +1225,9 @@
|
|||||||
"@executable_path/../../../../Frameworks",
|
"@executable_path/../../../../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
||||||
MARKETING_VERSION = 25.11.18;
|
MARKETING_VERSION = 25.11.26;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.ShareExtension;
|
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.ShareExtension;
|
||||||
PRODUCT_NAME = "Save to HotPocket Development";
|
PRODUCT_NAME = "Save to HotPocket";
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
@@ -1241,7 +1241,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = "macOS (Share Extension)/macOS (Share Extension).entitlements";
|
CODE_SIGN_ENTITLEMENTS = "macOS (Share Extension)/macOS (Share Extension).entitlements";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 2025111801;
|
CURRENT_PROJECT_VERSION = 2025112601;
|
||||||
DEVELOPMENT_TEAM = 648728X64K;
|
DEVELOPMENT_TEAM = 648728X64K;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -1255,7 +1255,7 @@
|
|||||||
"@executable_path/../../../../Frameworks",
|
"@executable_path/../../../../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
||||||
MARKETING_VERSION = 25.11.18;
|
MARKETING_VERSION = 25.11.26;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.ShareExtension;
|
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.ShareExtension;
|
||||||
PRODUCT_NAME = "Save to HotPocket";
|
PRODUCT_NAME = "Save to HotPocket";
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#import "HPAPI.h"
|
#import "HPAPI.h"
|
||||||
#import "HPCredentialsHelper.h"
|
#import "HPCredentialsHelper.h"
|
||||||
#import "HPRPCClient.h"
|
#import "HPRPCClient.h"
|
||||||
|
#import "NSBundle+HotPocketExtensions.h"
|
||||||
#import "NSURL+HotPocketExtensions.h"
|
#import "NSURL+HotPocketExtensions.h"
|
||||||
|
|
||||||
@implementation HPAuthParams
|
@implementation HPAuthParams
|
||||||
@@ -77,18 +78,19 @@
|
|||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSDictionary *postAuthenticateURLParams = [[[NSBundle mainBundle] infoDictionary] valueForKey:@"HPAuthFlowPostAuthenticateURLParts"];
|
NSString *expectedScheme = [NSBundle postAuthenticateURLScheme];
|
||||||
if (postAuthenticateURLParams == nil) {
|
NSString *expectedHost = [NSBundle postAuthenticateURLHost];
|
||||||
|
if (expectedScheme == nil || expectedHost == nil) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
|
NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
|
||||||
|
|
||||||
if ([urlComponents.scheme isEqualToString:[postAuthenticateURLParams valueForKey:@"scheme"]] == NO) {
|
if ([urlComponents.scheme isEqualToString:expectedScheme] == NO) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ([urlComponents.host isEqualToString:[postAuthenticateURLParams valueForKey:@"host"]] == NO) {
|
if ([urlComponents.host isEqualToString:expectedHost] == NO) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +111,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
-(BOOL)handleAuthParams:(HPAuthParams *)authParams {
|
-(BOOL)handleAuthParams:(HPAuthParams *)authParams {
|
||||||
|
[[NSNotificationCenter defaultCenter] postNotificationName:@"AuthFlowDidReceiveAuthParams" object:self];
|
||||||
|
|
||||||
HPRPCClient *rpcClient = [[HPRPCClient alloc] initWithBaseURL:self.baseURL accessToken:nil];
|
HPRPCClient *rpcClient = [[HPRPCClient alloc] initWithBaseURL:self.baseURL accessToken:nil];
|
||||||
|
|
||||||
NSArray *callParams = @[
|
NSArray *callParams = @[
|
||||||
@@ -120,7 +124,7 @@
|
|||||||
method:@"accounts.access_tokens.create"
|
method:@"accounts.access_tokens.create"
|
||||||
params:callParams endopoint:@"/accounts/rpc/"
|
params:callParams endopoint:@"/accounts/rpc/"
|
||||||
completionHandler:^(NSString *callId, HPRPCCallResult *result) {
|
completionHandler:^(NSString *callId, HPRPCCallResult *result) {
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||||
if (result.error != nil) {
|
if (result.error != nil) {
|
||||||
NSLog(@"-[HPAuthFlow handleAuthParams:] error=`%@`", result.error);
|
NSLog(@"-[HPAuthFlow handleAuthParams:] error=`%@`", result.error);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
@interface NSBundle (HotPocketExtensions)
|
@interface NSBundle (HotPocketExtensions)
|
||||||
|
|
||||||
+(NSString *)uname;
|
+(NSString *)uname;
|
||||||
|
+(NSDictionary *)postAuthenticateURLParams;
|
||||||
|
+(NSString *)postAuthenticateURLScheme;
|
||||||
|
+(NSString *)postAuthenticateURLHost;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|||||||
@@ -14,4 +14,23 @@
|
|||||||
return [NSString stringWithFormat:@"HotPocket v%@ (%@)", [mainBundle.infoDictionary valueForKey:@"CFBundleShortVersionString"], [mainBundle.infoDictionary valueForKey:@"CFBundleVersion"]];
|
return [NSString stringWithFormat:@"HotPocket v%@ (%@)", [mainBundle.infoDictionary valueForKey:@"CFBundleShortVersionString"], [mainBundle.infoDictionary valueForKey:@"CFBundleVersion"]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
+(NSDictionary *)postAuthenticateURLParams {
|
||||||
|
NSDictionary *result = [[[NSBundle mainBundle] infoDictionary] valueForKey:@"HPAuthFlowPostAuthenticateURLParts"];
|
||||||
|
if (result == nil) {
|
||||||
|
return [NSDictionary dictionary];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
+(NSString *)postAuthenticateURLScheme {
|
||||||
|
NSDictionary *params = [self postAuthenticateURLParams];
|
||||||
|
return [params valueForKey:@"scheme"];
|
||||||
|
}
|
||||||
|
|
||||||
|
+(NSString *)postAuthenticateURLHost {
|
||||||
|
NSDictionary *params = [self postAuthenticateURLParams];
|
||||||
|
return [params valueForKey:@"host"];
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -6,12 +6,19 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import <UIKit/UIKit.h>
|
#import <UIKit/UIKit.h>
|
||||||
|
#import <AuthenticationServices/AuthenticationServices.h>
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
@interface AuthorizationProgressViewController : UIViewController
|
@class MultilineLabel;
|
||||||
|
|
||||||
|
@interface AuthorizationProgressViewController : UIViewController<ASWebAuthenticationPresentationContextProviding>
|
||||||
|
|
||||||
@property IBOutlet UIActivityIndicatorView *progressIndicator;
|
@property IBOutlet UIActivityIndicatorView *progressIndicator;
|
||||||
|
@property IBOutlet MultilineLabel *progressLabel;
|
||||||
|
@property (strong, nullable) NSURL *authorizationURL;
|
||||||
|
@property (strong, nullable) ASWebAuthenticationSession *webAuthenticationSession;
|
||||||
|
@property BOOL userCancelledSession;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|||||||
@@ -8,20 +8,36 @@
|
|||||||
#import "AuthorizationProgressViewController.h"
|
#import "AuthorizationProgressViewController.h"
|
||||||
|
|
||||||
#import "AppDelegate.h"
|
#import "AppDelegate.h"
|
||||||
|
#import "HPAuthFlow.h"
|
||||||
#import "HPCredentialsHelper.h"
|
#import "HPCredentialsHelper.h"
|
||||||
|
#import "MultilineLabel.h"
|
||||||
|
#import "NSBundle+HotPocketExtensions.h"
|
||||||
|
|
||||||
@interface AuthorizationProgressViewController (AuthorizationProgressViewControllerPrivate)
|
@interface AuthorizationProgressViewController (AuthorizationProgressViewControllerPrivate)
|
||||||
|
|
||||||
#pragma mark - Private interface
|
#pragma mark - Private interface
|
||||||
|
|
||||||
|
-(void)presentAuthorizationError;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation AuthorizationProgressViewController
|
@implementation AuthorizationProgressViewController
|
||||||
|
|
||||||
#pragma mark - View lifecycle
|
#pragma mark - View lifecycle
|
||||||
|
|
||||||
|
-(instancetype)initWithCoder:(NSCoder *)coder {
|
||||||
|
if (self = [super initWithCoder:coder]) {
|
||||||
|
self.authorizationURL = nil;
|
||||||
|
self.webAuthenticationSession = nil;
|
||||||
|
self.userCancelledSession = NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
-(void)viewDidLoad {
|
-(void)viewDidLoad {
|
||||||
[super viewDidLoad];
|
[super viewDidLoad];
|
||||||
|
self.progressLabel.text = NSLocalizedString(@"Continue to sign in in your browser...", @"Continue to sign in in your browser...");
|
||||||
}
|
}
|
||||||
|
|
||||||
-(void)viewWillAppear:(BOOL)animated {
|
-(void)viewWillAppear:(BOOL)animated {
|
||||||
@@ -29,17 +45,90 @@
|
|||||||
[self.progressIndicator startAnimating];
|
[self.progressIndicator startAnimating];
|
||||||
|
|
||||||
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
|
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAuthFlowDidFinish:) name:@"AuthFlowDidFinish" object:appDelegate.authFlow];
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
|
selector:@selector(onAuthFlowDidFinish:)
|
||||||
|
name:@"AuthFlowDidFinish"
|
||||||
|
object:appDelegate.authFlow];
|
||||||
|
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
|
selector:@selector(onAuthFlowDidReceiveAuthParams:)
|
||||||
|
name:@"AuthFlowDidReceiveAuthParams"
|
||||||
|
object:appDelegate.authFlow];
|
||||||
}
|
}
|
||||||
|
|
||||||
-(void)viewWillDisappear:(BOOL)animated {
|
-(void)viewDidAppear:(BOOL)animated {
|
||||||
[super viewWillDisappear:animated];
|
[super viewDidAppear:animated];
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
|
||||||
|
|
||||||
|
ASWebAuthenticationSessionCompletionHandler completionHandler = ^(NSURL *url, NSError *error) {
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
if (error != nil) {
|
||||||
|
#ifdef DEBUG
|
||||||
|
NSLog(@"[AuthorizationViewController.session completionHandler] error=`%@`", error);
|
||||||
|
#endif
|
||||||
|
if (error.code == ASWebAuthenticationSessionErrorCodeCanceledLogin) {
|
||||||
|
self.userCancelledSession = YES;
|
||||||
|
}
|
||||||
|
[self presentAuthorizationError];
|
||||||
|
} else {
|
||||||
|
HPAuthParams *receivedAuthParams = [appDelegate.authFlow handlePostAuthenticateURL:url];
|
||||||
|
if (receivedAuthParams != nil) {
|
||||||
|
[appDelegate.authFlow handleAuthParams:receivedAuthParams];
|
||||||
|
} else {
|
||||||
|
[self presentAuthorizationError];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.webAuthenticationSession = nil;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
ASWebAuthenticationSessionCallback *callback = [ASWebAuthenticationSessionCallback callbackWithCustomScheme:[NSBundle postAuthenticateURLScheme]];
|
||||||
|
self.webAuthenticationSession = [[ASWebAuthenticationSession alloc] initWithURL:self.authorizationURL
|
||||||
|
callback:callback
|
||||||
|
completionHandler:completionHandler];
|
||||||
|
self.webAuthenticationSession.presentationContextProvider = self;
|
||||||
|
#ifdef DEBUG
|
||||||
|
self.webAuthenticationSession.prefersEphemeralWebBrowserSession = YES;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (self.webAuthenticationSession.canStart == NO) {
|
||||||
|
[self presentAuthorizationError];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[self.webAuthenticationSession start];
|
||||||
}
|
}
|
||||||
|
|
||||||
-(void)viewDidDisappear:(BOOL)animated {
|
-(void)viewDidDisappear:(BOOL)animated {
|
||||||
[super viewDidDisappear:animated];
|
[super viewDidDisappear:animated];
|
||||||
|
self.webAuthenticationSession = nil;
|
||||||
|
|
||||||
[self.progressIndicator stopAnimating];
|
[self.progressIndicator stopAnimating];
|
||||||
|
|
||||||
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
# pragma mark - Private interface
|
||||||
|
|
||||||
|
-(void)presentAuthorizationError {
|
||||||
|
if (self.userCancelledSession == NO) {
|
||||||
|
UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Oops!", @"Oops!")
|
||||||
|
message:NSLocalizedString(@"HotPocket couldn't complete this operation.", @"HotPocket couldn't complete this operation.")
|
||||||
|
preferredStyle:UIAlertControllerStyleAlert];
|
||||||
|
|
||||||
|
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Oh well", @"Oh well")
|
||||||
|
style:UIAlertActionStyleDefault
|
||||||
|
handler:^(UIAlertAction *action) {
|
||||||
|
[alert dismissViewControllerAnimated:YES completion:^{
|
||||||
|
[self.navigationController popViewControllerAnimated:YES];
|
||||||
|
}];
|
||||||
|
}]];
|
||||||
|
|
||||||
|
[self presentViewController:alert animated:YES completion:nil];
|
||||||
|
} else {
|
||||||
|
[self.navigationController popViewControllerAnimated:YES];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Notification handlers
|
#pragma mark - Notification handlers
|
||||||
@@ -52,23 +141,21 @@
|
|||||||
HPCredentials *credentials = [[HPCredentialsHelper sharedHelper] getCredentials];
|
HPCredentials *credentials = [[HPCredentialsHelper sharedHelper] getCredentials];
|
||||||
|
|
||||||
if (credentials.usable == NO) {
|
if (credentials.usable == NO) {
|
||||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Oops!", @"Oops!")
|
[self presentAuthorizationError];
|
||||||
message:NSLocalizedString(@"HotPocket couldn't complete this operation.", @"HotPocket couldn't complete this operation.")
|
|
||||||
preferredStyle:UIAlertControllerStyleAlert];
|
|
||||||
|
|
||||||
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Oh well", @"Oh well")
|
|
||||||
style:UIAlertActionStyleDefault
|
|
||||||
handler:^(UIAlertAction *action) {
|
|
||||||
[alert dismissViewControllerAnimated:YES completion:^{
|
|
||||||
[self.navigationController popViewControllerAnimated:YES];
|
|
||||||
}];
|
|
||||||
}]];
|
|
||||||
|
|
||||||
[self presentViewController:alert animated:YES completion:nil];
|
|
||||||
} else {
|
} else {
|
||||||
[self.navigationController popToRootViewControllerAnimated:YES];
|
[self.navigationController popToRootViewControllerAnimated:YES];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-(void)onAuthFlowDidReceiveAuthParams:(NSNotification *)notification {
|
||||||
|
self.progressLabel.text = NSLocalizedString(@"Processing authorization...", @"Processing authorization...");
|
||||||
|
}
|
||||||
|
|
||||||
|
# pragma mark - ASWebAuthenticationPresentationContextProviding implementation
|
||||||
|
|
||||||
|
-(ASPresentationAnchor)presentationAnchorForWebAuthenticationSession:(ASWebAuthenticationSession *)session {
|
||||||
|
return self.view.window;
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -73,14 +73,9 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ([application canOpenURL:authURL] == YES) {
|
AuthorizationProgressViewController *authorizationProgressViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"AuthorizationProgressViewController"];
|
||||||
[application openURL:authURL options:@{} completionHandler:^(BOOL result) {
|
authorizationProgressViewController.authorizationURL = authURL;
|
||||||
if (result == YES) {
|
[self.navigationController pushViewController:authorizationProgressViewController animated:YES];
|
||||||
AuthorizationProgressViewController *authorizationProgressViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"AuthorizationProgressViewController"];
|
|
||||||
[self.navigationController pushViewController:authorizationProgressViewController animated:YES];
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Event handlers
|
#pragma mark - Event handlers
|
||||||
|
|||||||
@@ -229,8 +229,8 @@
|
|||||||
<rect key="frame" x="189" y="306" width="37" height="37"/>
|
<rect key="frame" x="189" y="306" width="37" height="37"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
</activityIndicatorView>
|
</activityIndicatorView>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Awaiting authentication response..." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qiJ-yx-nMd">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Awaiting authentication response..." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qiJ-yx-nMd" customClass="MultilineLabel">
|
||||||
<rect key="frame" x="20" y="359" width="374" height="21"/>
|
<rect key="frame" x="20" y="359" width="374" height="64"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
@@ -242,6 +242,7 @@
|
|||||||
</view>
|
</view>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="progressIndicator" destination="DNy-gf-n60" id="hJF-jc-ZJ0"/>
|
<outlet property="progressIndicator" destination="DNy-gf-n60" id="hJF-jc-ZJ0"/>
|
||||||
|
<outlet property="progressLabel" destination="qiJ-yx-nMd" id="1Wu-em-XsK"/>
|
||||||
</connections>
|
</connections>
|
||||||
</viewController>
|
</viewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="N3D-cM-5Ro" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="N3D-cM-5Ro" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||||
|
|||||||
@@ -21,18 +21,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
-(void)application:(NSApplication *)application openURLs:(NSArray<NSURL *> *)urls {
|
-(void)application:(NSApplication *)application openURLs:(NSArray<NSURL *> *)urls {
|
||||||
HPAuthParams *receivedAuthParams = nil;
|
|
||||||
for (NSURL *url in urls) {
|
|
||||||
receivedAuthParams = [self.authFlow handlePostAuthenticateURL:url];
|
|
||||||
|
|
||||||
if (receivedAuthParams != nil) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (receivedAuthParams != nil) {
|
|
||||||
[self.authFlow handleAuthParams:receivedAuthParams];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -6,12 +6,19 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#import <AuthenticationServices/AuthenticationServices.h>
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
@interface AuthorizationProgressViewController : NSViewController
|
@interface AuthorizationProgressViewController : NSViewController<ASWebAuthenticationPresentationContextProviding>
|
||||||
|
|
||||||
@property IBOutlet NSProgressIndicator *progressIndicator;
|
@property IBOutlet NSProgressIndicator *progressIndicator;
|
||||||
|
@property NSString *progressLabelTitle;
|
||||||
|
@property (nullable, strong) NSURL *authorizationURL;
|
||||||
|
@property (nullable, strong) ASWebAuthenticationSession *webAuthenticationSession;
|
||||||
|
@property BOOL userCancelledSession;
|
||||||
|
|
||||||
|
-(IBAction)doCancel:(id)sender;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|||||||
@@ -9,37 +9,133 @@
|
|||||||
|
|
||||||
#import "AppDelegate.h"
|
#import "AppDelegate.h"
|
||||||
#import "AuthorizationViewController.h"
|
#import "AuthorizationViewController.h"
|
||||||
|
#import "HPAuthFlow.h"
|
||||||
#import "HPCredentialsHelper.h"
|
#import "HPCredentialsHelper.h"
|
||||||
#import "MainViewController.h"
|
#import "MainViewController.h"
|
||||||
|
#import "NSBundle+HotPocketExtensions.h"
|
||||||
#import "ReplaceAnimator.h"
|
#import "ReplaceAnimator.h"
|
||||||
|
|
||||||
@interface AuthorizationProgressViewController (AuthorizationProgressViewControllerPrivate)
|
@interface AuthorizationProgressViewController (AuthorizationProgressViewControllerPrivate)
|
||||||
|
|
||||||
#pragma mark - Private interface
|
#pragma mark - Private interface
|
||||||
|
|
||||||
|
-(void)presentAuthorizationError;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation AuthorizationProgressViewController
|
@implementation AuthorizationProgressViewController
|
||||||
|
|
||||||
#pragma mark - View lifecycle
|
#pragma mark - View lifecycle
|
||||||
|
|
||||||
|
-(instancetype)initWithCoder:(NSCoder *)coder {
|
||||||
|
if (self = [super initWithCoder:coder]) {
|
||||||
|
self.authorizationURL = nil;
|
||||||
|
self.webAuthenticationSession = nil;
|
||||||
|
self.userCancelledSession = NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
-(void)viewDidLoad {
|
-(void)viewDidLoad {
|
||||||
[super viewDidLoad];
|
[super viewDidLoad];
|
||||||
|
self.progressLabelTitle = NSLocalizedString(@"Continue to sign in in your browser...", @"Continue to sign in in your browser...");
|
||||||
}
|
}
|
||||||
|
|
||||||
-(void)viewWillAppear {
|
-(void)viewWillAppear {
|
||||||
AppDelegate *appDelegate = [[NSApplication sharedApplication] delegate];
|
AppDelegate *appDelegate = [[NSApplication sharedApplication] delegate];
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAuthFlowDidFinish:) name:@"AuthFlowDidFinish" object:appDelegate.authFlow];
|
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
|
selector:@selector(onAuthFlowDidFinish:)
|
||||||
|
name:@"AuthFlowDidFinish"
|
||||||
|
object:appDelegate.authFlow];
|
||||||
|
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
|
selector:@selector(onAuthFlowDidReceiveAuthParams:)
|
||||||
|
name:@"AuthFlowDidReceiveAuthParams"
|
||||||
|
object:appDelegate.authFlow];
|
||||||
|
|
||||||
[self.progressIndicator startAnimation:self];
|
[self.progressIndicator startAnimation:self];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-(void)viewDidAppear {
|
||||||
|
[super viewDidAppear];
|
||||||
|
AppDelegate *appDelegate = [[NSApplication sharedApplication] delegate];
|
||||||
|
|
||||||
|
ASWebAuthenticationSessionCompletionHandler completionHandler = ^(NSURL *url, NSError *error) {
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
if (error != nil) {
|
||||||
|
#ifdef DEBUG
|
||||||
|
NSLog(@"[AuthorizationViewController.session completionHandler] error=`%@`", error);
|
||||||
|
#endif
|
||||||
|
if (error.code == ASWebAuthenticationSessionErrorCodeCanceledLogin) {
|
||||||
|
self.userCancelledSession = YES;
|
||||||
|
}
|
||||||
|
[self presentAuthorizationError];
|
||||||
|
} else {
|
||||||
|
HPAuthParams *receivedAuthParams = [appDelegate.authFlow handlePostAuthenticateURL:url];
|
||||||
|
if (receivedAuthParams != nil) {
|
||||||
|
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
||||||
|
[appDelegate.authFlow handleAuthParams:receivedAuthParams];
|
||||||
|
} else {
|
||||||
|
[self presentAuthorizationError];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.webAuthenticationSession = nil;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
ASWebAuthenticationSessionCallback *callback = [ASWebAuthenticationSessionCallback callbackWithCustomScheme:[NSBundle postAuthenticateURLScheme]];
|
||||||
|
self.webAuthenticationSession = [[ASWebAuthenticationSession alloc] initWithURL:self.authorizationURL
|
||||||
|
callback:callback
|
||||||
|
completionHandler:completionHandler];
|
||||||
|
self.webAuthenticationSession.presentationContextProvider = self;
|
||||||
|
#ifdef DEBUG
|
||||||
|
self.webAuthenticationSession.prefersEphemeralWebBrowserSession = YES;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (self.webAuthenticationSession.canStart == NO) {
|
||||||
|
[self presentAuthorizationError];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[self.webAuthenticationSession start];
|
||||||
|
}
|
||||||
|
|
||||||
-(void)viewDidDisappear {
|
-(void)viewDidDisappear {
|
||||||
|
[super viewDidDisappear];
|
||||||
|
self.webAuthenticationSession = nil;
|
||||||
|
|
||||||
[self.progressIndicator stopAnimation:self];
|
[self.progressIndicator stopAnimation:self];
|
||||||
|
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma mark - Actions
|
||||||
|
|
||||||
|
-(IBAction)doCancel:(id)sender {
|
||||||
|
[self.webAuthenticationSession cancel];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Private interface
|
||||||
|
|
||||||
|
-(void)presentAuthorizationError {
|
||||||
|
if (self.userCancelledSession == NO) {
|
||||||
|
NSAlert *alert = [[NSAlert alloc] init];
|
||||||
|
alert.alertStyle = NSAlertStyleCritical;
|
||||||
|
alert.messageText = NSLocalizedString(@"Oops!", @"Oops!");
|
||||||
|
alert.informativeText = NSLocalizedString(@"HotPocket couldn't complete this operation.", @"HotPocket couldn't complete this operation.");
|
||||||
|
[alert beginSheetModalForWindow:self.view.window completionHandler:^(NSModalResponse response) {
|
||||||
|
AuthorizationViewController *authorizationViewController = [self.storyboard instantiateControllerWithIdentifier:@"AuthorizationViewController"];
|
||||||
|
[self presentViewController:authorizationViewController animator:[[ReplaceAnimator alloc] init]];
|
||||||
|
}];
|
||||||
|
} else {
|
||||||
|
AuthorizationViewController *authorizationViewController = [self.storyboard instantiateControllerWithIdentifier:@"AuthorizationViewController"];
|
||||||
|
[self presentViewController:authorizationViewController animator:[[ReplaceAnimator alloc] init]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - Notification handlers
|
#pragma mark - Notification handlers
|
||||||
|
|
||||||
-(void)onAuthFlowDidFinish:(NSNotification *)notification {
|
-(void)onAuthFlowDidFinish:(NSNotification *)notification {
|
||||||
@@ -50,14 +146,7 @@
|
|||||||
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
||||||
|
|
||||||
if (credentials.usable == NO) {
|
if (credentials.usable == NO) {
|
||||||
NSAlert *alert = [[NSAlert alloc] init];
|
[self presentAuthorizationError];
|
||||||
alert.alertStyle = NSAlertStyleCritical;
|
|
||||||
alert.messageText = NSLocalizedString(@"Oops!", @"Oops!");
|
|
||||||
alert.informativeText = NSLocalizedString(@"HotPocket couldn't complete this operation.", @"HotPocket couldn't complete this operation.");
|
|
||||||
[alert runModal];
|
|
||||||
|
|
||||||
AuthorizationViewController *authorizationViewController = [self.storyboard instantiateControllerWithIdentifier:@"AuthorizationViewController"];
|
|
||||||
[self presentViewController:authorizationViewController animator:[[ReplaceAnimator alloc] init]];
|
|
||||||
} else {
|
} else {
|
||||||
MainViewController *mainViewController = [self.storyboard instantiateControllerWithIdentifier:@"MainViewController"];
|
MainViewController *mainViewController = [self.storyboard instantiateControllerWithIdentifier:@"MainViewController"];
|
||||||
[self presentViewController:mainViewController animator:[[ReplaceAnimator alloc] init]];
|
[self presentViewController:mainViewController animator:[[ReplaceAnimator alloc] init]];
|
||||||
@@ -65,4 +154,14 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-(void)onAuthFlowDidReceiveAuthParams:(NSNotification *)notification {
|
||||||
|
self.progressLabelTitle = NSLocalizedString(@"Processing authorization...", @"Processing authorization...");
|
||||||
|
}
|
||||||
|
|
||||||
|
# pragma mark - ASWebAuthenticationPresentationContextProviding implementation
|
||||||
|
|
||||||
|
-(ASPresentationAnchor)presentationAnchorForWebAuthenticationSession:(ASWebAuthenticationSession *)session {
|
||||||
|
return self.view.window;
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -31,19 +31,24 @@
|
|||||||
#pragma mark - Actions
|
#pragma mark - Actions
|
||||||
|
|
||||||
-(IBAction)doStartAuthorizationFlow:(id)sender {
|
-(IBAction)doStartAuthorizationFlow:(id)sender {
|
||||||
AppDelegate *appDeleate = [[NSApplication sharedApplication] delegate];
|
AppDelegate *appDelegate = [[NSApplication sharedApplication] delegate];
|
||||||
appDeleate.authFlow.baseURL = [NSURL URLWithString:self.baseURL];
|
appDelegate.authFlow.baseURL = [NSURL URLWithString:self.baseURL];
|
||||||
|
|
||||||
NSURL *authURL = [appDeleate.authFlow start];
|
NSURL *authURL = [appDelegate.authFlow start];
|
||||||
if (authURL == nil) {
|
if (authURL == nil) {
|
||||||
NSBeep();
|
NSBeep();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthorizationProgressViewController *authProgressViewController = [self.storyboard instantiateControllerWithIdentifier:@"AuthorizationProgressViewController"];
|
AuthorizationProgressViewController *authProgressViewController = [self.storyboard instantiateControllerWithIdentifier:@"AuthorizationProgressViewController"];
|
||||||
|
authProgressViewController.authorizationURL = authURL;
|
||||||
[self presentViewController:authProgressViewController animator:[[ReplaceAnimator alloc] init]];
|
[self presentViewController:authProgressViewController animator:[[ReplaceAnimator alloc] init]];
|
||||||
|
}
|
||||||
|
|
||||||
[[NSWorkspace sharedWorkspace] openURL:authURL];
|
# pragma mark - ASWebAuthenticationPresentationContextProviding implementation
|
||||||
|
|
||||||
|
-(ASPresentationAnchor)presentationAnchorForWebAuthenticationSession:(ASWebAuthenticationSession *)session {
|
||||||
|
return self.view.window;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -103,7 +103,7 @@
|
|||||||
<rect key="frame" x="0.0" y="0.0" width="425" height="325"/>
|
<rect key="frame" x="0.0" y="0.0" width="425" height="325"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7sM-F3-Zzf">
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7sM-F3-Zzf">
|
||||||
<rect key="frame" x="18" y="153" width="389" height="16"/>
|
<rect key="frame" x="18" y="153" width="389" height="16"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||||
<textFieldCell key="cell" lineBreakMode="clipping" title="HotPocket Instance URL" id="XwM-DV-kei">
|
<textFieldCell key="cell" lineBreakMode="clipping" title="HotPocket Instance URL" id="XwM-DV-kei">
|
||||||
@@ -112,7 +112,7 @@
|
|||||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||||
</textFieldCell>
|
</textFieldCell>
|
||||||
</textField>
|
</textField>
|
||||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ygC-xe-m6y">
|
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ygC-xe-m6y">
|
||||||
<rect key="frame" x="20" y="124" width="385" height="21"/>
|
<rect key="frame" x="20" y="124" width="385" height="21"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" drawsBackground="YES" id="rHK-hP-yWO">
|
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" drawsBackground="YES" id="rHK-hP-yWO">
|
||||||
@@ -128,7 +128,7 @@
|
|||||||
</binding>
|
</binding>
|
||||||
</connections>
|
</connections>
|
||||||
</textField>
|
</textField>
|
||||||
<textField focusRingType="none" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DIc-8O-uoQ">
|
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DIc-8O-uoQ">
|
||||||
<rect key="frame" x="18" y="68" width="389" height="48"/>
|
<rect key="frame" x="18" y="68" width="389" height="48"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||||
<textFieldCell key="cell" selectable="YES" title="Enter the URL to your HotPocket instance, e.g. https://hotpocket.yourcompany.com/" id="Y0q-a1-oBP">
|
<textFieldCell key="cell" selectable="YES" title="Enter the URL to your HotPocket instance, e.g. https://hotpocket.yourcompany.com/" id="Y0q-a1-oBP">
|
||||||
@@ -154,7 +154,7 @@
|
|||||||
<action selector="doStartAuthorizationFlow:" target="XfG-lQ-9wD" id="AOi-Wt-gmL"/>
|
<action selector="doStartAuthorizationFlow:" target="XfG-lQ-9wD" id="AOi-Wt-gmL"/>
|
||||||
</connections>
|
</connections>
|
||||||
</button>
|
</button>
|
||||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="mQc-Ea-NNN">
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="mQc-Ea-NNN">
|
||||||
<rect key="frame" x="18" y="185" width="389" height="28"/>
|
<rect key="frame" x="18" y="185" width="389" height="28"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="HotPocket by BTHLabs" id="NTZ-zl-yhk">
|
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="HotPocket by BTHLabs" id="NTZ-zl-yhk">
|
||||||
@@ -177,7 +177,7 @@
|
|||||||
<rect key="frame" x="0.0" y="0.0" width="425" height="325"/>
|
<rect key="frame" x="0.0" y="0.0" width="425" height="325"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="yRj-hC-QYS">
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="yRj-hC-QYS">
|
||||||
<rect key="frame" x="18" y="185" width="389" height="28"/>
|
<rect key="frame" x="18" y="185" width="389" height="28"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="HotPocket by BTHLabs" id="F4l-2Z-D79">
|
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="HotPocket by BTHLabs" id="F4l-2Z-D79">
|
||||||
@@ -195,15 +195,32 @@
|
|||||||
<rect key="frame" x="196" y="113" width="32" height="32"/>
|
<rect key="frame" x="196" y="113" width="32" height="32"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
</progressIndicator>
|
</progressIndicator>
|
||||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="g9a-gR-c7o">
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="g9a-gR-c7o">
|
||||||
<rect key="frame" x="18" y="81" width="389" height="16"/>
|
<rect key="frame" x="18" y="49" width="389" height="48"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="Awaiting authorization response..." id="3oi-LK-vKv">
|
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="Awaiting authorization response..." id="3oi-LK-vKv">
|
||||||
<font key="font" metaFont="system"/>
|
<font key="font" metaFont="system"/>
|
||||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||||
</textFieldCell>
|
</textFieldCell>
|
||||||
|
<connections>
|
||||||
|
<binding destination="OX4-Oj-1cw" name="value" keyPath="self.progressLabelTitle" id="ydU-jy-p3F"/>
|
||||||
|
</connections>
|
||||||
</textField>
|
</textField>
|
||||||
|
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Sip-bU-V5i">
|
||||||
|
<rect key="frame" x="175" y="14" width="76" height="32"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
|
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="nYJ-LO-V7O">
|
||||||
|
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
<string key="keyEquivalent" base64-UTF8="YES">
|
||||||
|
Gw
|
||||||
|
</string>
|
||||||
|
</buttonCell>
|
||||||
|
<connections>
|
||||||
|
<action selector="doCancel:" target="OX4-Oj-1cw" id="nAC-If-aib"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
</subviews>
|
</subviews>
|
||||||
</view>
|
</view>
|
||||||
<connections>
|
<connections>
|
||||||
@@ -227,7 +244,7 @@
|
|||||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" animates="YES" imageScaling="proportionallyDown" image="icon-mac-384" id="fae-mz-0sj"/>
|
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" animates="YES" imageScaling="proportionallyDown" image="icon-mac-384" id="fae-mz-0sj"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T7q-KB-3Ut">
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T7q-KB-3Ut">
|
||||||
<rect key="frame" x="18" y="185" width="389" height="28"/>
|
<rect key="frame" x="18" y="185" width="389" height="28"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="HotPocket by BTHLabs" id="r5O-Sk-IdK">
|
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="HotPocket by BTHLabs" id="r5O-Sk-IdK">
|
||||||
@@ -236,7 +253,7 @@
|
|||||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||||
</textFieldCell>
|
</textFieldCell>
|
||||||
</textField>
|
</textField>
|
||||||
<textField focusRingType="none" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2h7-bN-dsa">
|
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2h7-bN-dsa">
|
||||||
<rect key="frame" x="18" y="153" width="389" height="16"/>
|
<rect key="frame" x="18" y="153" width="389" height="16"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||||
<textFieldCell key="cell" selectable="YES" title="HotPocket is configured and ready." id="5fh-mh-WR1">
|
<textFieldCell key="cell" selectable="YES" title="HotPocket is configured and ready." id="5fh-mh-WR1">
|
||||||
@@ -245,7 +262,7 @@
|
|||||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||||
</textFieldCell>
|
</textFieldCell>
|
||||||
</textField>
|
</textField>
|
||||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="uci-UC-wxo">
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="uci-UC-wxo">
|
||||||
<rect key="frame" x="18" y="89" width="389" height="16"/>
|
<rect key="frame" x="18" y="89" width="389" height="16"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||||
<textFieldCell key="cell" lineBreakMode="clipping" title="Instance URL" id="azk-ea-KeN">
|
<textFieldCell key="cell" lineBreakMode="clipping" title="Instance URL" id="azk-ea-KeN">
|
||||||
@@ -267,8 +284,8 @@
|
|||||||
<binding destination="r5D-xE-cNT" name="enabled" keyPath="self.logoutButtonEnabled" id="gTs-BO-USz"/>
|
<binding destination="r5D-xE-cNT" name="enabled" keyPath="self.logoutButtonEnabled" id="gTs-BO-USz"/>
|
||||||
</connections>
|
</connections>
|
||||||
</button>
|
</button>
|
||||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="8H3-oU-acU" customClass="LinkLabel">
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="8H3-oU-acU" customClass="LinkLabel">
|
||||||
<rect key="frame" x="18" y="65" width="389" height="16"/>
|
<rect key="frame" x="18" y="69" width="389" height="16"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||||
<textFieldCell key="cell" lineBreakMode="clipping" allowsEditingTextAttributes="YES" id="EoA-mM-phM">
|
<textFieldCell key="cell" lineBreakMode="clipping" allowsEditingTextAttributes="YES" id="EoA-mM-phM">
|
||||||
<font key="font" metaFont="system"/>
|
<font key="font" metaFont="system"/>
|
||||||
@@ -276,7 +293,7 @@
|
|||||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||||
</textFieldCell>
|
</textFieldCell>
|
||||||
</textField>
|
</textField>
|
||||||
<textField focusRingType="none" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9pl-Ap-yxc">
|
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9pl-Ap-yxc">
|
||||||
<rect key="frame" x="18" y="113" width="389" height="32"/>
|
<rect key="frame" x="18" y="113" width="389" height="32"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
|
||||||
<textFieldCell key="cell" selectable="YES" title="Safari and Share Extensions are installed." id="dy7-bw-DYh">
|
<textFieldCell key="cell" selectable="YES" title="Safari and Share Extensions are installed." id="dy7-bw-DYh">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "hotpocket-apple"
|
name = "hotpocket-apple"
|
||||||
version = "25.11.18"
|
version = "25.11.26"
|
||||||
description = "HotPocket Apple Integrations"
|
description = "HotPocket Apple Integrations"
|
||||||
authors = ["Tomek Wójcik <contact@bthlabs.pl>"]
|
authors = ["Tomek Wójcik <contact@bthlabs.pl>"]
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ COPY --chown=$APP_USER_UID:$APP_USER_GID packages/common/ /srv/packages/common/
|
|||||||
COPY --chown=$APP_USER_UID:$APP_USER_GID packages/soa/ /srv/packages/soa/
|
COPY --chown=$APP_USER_UID:$APP_USER_GID packages/soa/ /srv/packages/soa/
|
||||||
|
|
||||||
RUN poetry install --only main,deployment && \
|
RUN poetry install --only main,deployment && \
|
||||||
minify -i --css-precision 0 --js-precision 0 --js-version 2022 hotpocket_backend/apps/ui/static/ui/css/hotpocket-backend*.css hotpocket_backend/apps/ui/static/ui/js/hotpocket*.js && \
|
minify -i --css-precision 0 --js-precision 0 --js-version 2022 hotpocket_backend/apps/ui/static/ui/css/hotpocket-backend*.css hotpocket_backend/apps/ui/static/ui/js/hotpocket-backend*.js && \
|
||||||
./manage.py collectstatic --settings hotpocket_backend.settings.deployment.build --noinput && \
|
./manage.py collectstatic --settings hotpocket_backend.settings.deployment.build --noinput && \
|
||||||
find hotpocket_backend/static/ -name "*.map*" -delete && \
|
find hotpocket_backend/static/ -name "*.map*" -delete && \
|
||||||
rm -f hotpocket_backend/settings/deployment/build.py && \
|
rm -f hotpocket_backend/settings/deployment/build.py && \
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
version = '25.11.18'
|
version = '25.12.04'
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
from .access_token import AccessToken # noqa: F401
|
from .access_token import AccessToken # noqa: F401
|
||||||
from .account import AccountAdmin # noqa: F401
|
from .account import AccountAdmin # noqa: F401
|
||||||
|
from .auth_key import AuthKey # noqa: F401
|
||||||
|
|||||||
@@ -2,16 +2,41 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from hotpocket_backend.apps.accounts.models import AccessToken
|
from hotpocket_backend.apps.accounts.models import AccessToken
|
||||||
|
|
||||||
|
|
||||||
class AccessTokenAdmin(admin.ModelAdmin):
|
class AccessTokenAdmin(admin.ModelAdmin):
|
||||||
list_display = ('pk', 'account_uuid', 'origin', 'created_at', 'is_active')
|
list_display = (
|
||||||
|
'pk', 'account_uuid', 'origin', 'created_at', 'render_is_active',
|
||||||
|
)
|
||||||
search_fields = ('pk', 'account_uuid', 'key', 'origin')
|
search_fields = ('pk', 'account_uuid', 'key', 'origin')
|
||||||
|
readonly_fields = (
|
||||||
|
'pk',
|
||||||
|
'account_uuid',
|
||||||
|
'key',
|
||||||
|
'origin',
|
||||||
|
'meta',
|
||||||
|
'created_at',
|
||||||
|
'deleted_at',
|
||||||
|
)
|
||||||
|
ordering = ['-created_at']
|
||||||
|
|
||||||
|
def has_change_permission(self, request, obj=None):
|
||||||
|
return False
|
||||||
|
|
||||||
def has_delete_permission(self, request, obj=None):
|
def has_delete_permission(self, request, obj=None):
|
||||||
return request.user.is_superuser
|
return request.user.is_superuser
|
||||||
|
|
||||||
|
@admin.display(
|
||||||
|
description=_('Is Active?'), boolean=True, ordering='-deleted_at',
|
||||||
|
)
|
||||||
|
def render_is_active(self, obj: AccessToken | None = None) -> bool | None:
|
||||||
|
if obj is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return obj.is_active
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(AccessToken, AccessTokenAdmin)
|
admin.site.register(AccessToken, AccessTokenAdmin)
|
||||||
|
|||||||
@@ -3,15 +3,26 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth.admin import UserAdmin
|
from django.contrib.auth.admin import UserAdmin
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from hotpocket_backend.apps.accounts.models import Account
|
from hotpocket_backend.apps.accounts.models import Account
|
||||||
|
|
||||||
|
|
||||||
class AccountAdmin(UserAdmin):
|
class AccountAdmin(UserAdmin):
|
||||||
list_display = (*UserAdmin.list_display, 'is_active')
|
list_display = (*UserAdmin.list_display, 'render_is_active')
|
||||||
|
ordering = ['username']
|
||||||
|
|
||||||
def has_delete_permission(self, request, obj=None):
|
def has_delete_permission(self, request, obj=None):
|
||||||
return request.user.is_superuser
|
return request.user.is_superuser
|
||||||
|
|
||||||
|
@admin.display(
|
||||||
|
description=_('Is Active?'), boolean=True, ordering='-is_active',
|
||||||
|
)
|
||||||
|
def render_is_active(self, obj: Account | None = None) -> bool | None:
|
||||||
|
if obj is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return obj.is_active
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Account, AccountAdmin)
|
admin.site.register(Account, AccountAdmin)
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from hotpocket_backend.apps.accounts.models import AuthKey
|
||||||
|
|
||||||
|
|
||||||
|
class AuthKeyAdmin(admin.ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
'pk', 'account_uuid', 'key', 'created_at', 'consumed_at', 'render_is_active',
|
||||||
|
)
|
||||||
|
search_fields = ('pk', 'account_uuid', 'key')
|
||||||
|
readonly_fields = (
|
||||||
|
'pk',
|
||||||
|
'account_uuid',
|
||||||
|
'key',
|
||||||
|
'consumed_at',
|
||||||
|
'created_at',
|
||||||
|
'deleted_at',
|
||||||
|
)
|
||||||
|
ordering = ['-created_at']
|
||||||
|
|
||||||
|
def has_change_permission(self, request, obj=None):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def has_delete_permission(self, request, obj=None):
|
||||||
|
return request.user.is_superuser
|
||||||
|
|
||||||
|
@admin.display(
|
||||||
|
description=_('Is Active?'), boolean=True, ordering='-deleted_at',
|
||||||
|
)
|
||||||
|
def render_is_active(self, obj: AuthKey | None = None) -> bool | None:
|
||||||
|
if obj is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return obj.is_active
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(AuthKey, AuthKeyAdmin)
|
||||||
@@ -38,3 +38,5 @@ class PSettings(typing.Protocol):
|
|||||||
|
|
||||||
UI_PAGE_HEAD_INCLUDES: list
|
UI_PAGE_HEAD_INCLUDES: list
|
||||||
UI_PAGE_SCRIPT_INCLUDES: list
|
UI_PAGE_SCRIPT_INCLUDES: list
|
||||||
|
|
||||||
|
OPERATOR_EMAIL: str | None
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
|
from . import association # noqa: F401
|
||||||
from . import save # noqa: F401
|
from . import save # noqa: F401
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from hotpocket_backend.apps.saves.models import Association
|
||||||
|
|
||||||
|
|
||||||
|
class AssociationAdmin(admin.ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
'pk', 'account_uuid', 'target', 'created_at', 'render_is_active',
|
||||||
|
)
|
||||||
|
search_fields = ('pk', 'account_uuid')
|
||||||
|
fields = (
|
||||||
|
'pk',
|
||||||
|
'account_uuid',
|
||||||
|
'archived_at',
|
||||||
|
'starred_at',
|
||||||
|
'target_meta',
|
||||||
|
'target_title',
|
||||||
|
'target_description',
|
||||||
|
'created_at',
|
||||||
|
'deleted_at',
|
||||||
|
)
|
||||||
|
readonly_fields = (
|
||||||
|
'pk',
|
||||||
|
'account_uuid',
|
||||||
|
'target_meta',
|
||||||
|
'target',
|
||||||
|
'archived_at',
|
||||||
|
'starred_at',
|
||||||
|
'created_at',
|
||||||
|
'deleted_at',
|
||||||
|
)
|
||||||
|
ordering = ['-created_at']
|
||||||
|
|
||||||
|
def has_delete_permission(self, request, obj=None):
|
||||||
|
return request.user.is_superuser
|
||||||
|
|
||||||
|
@admin.display(
|
||||||
|
description=_('Is Active?'), boolean=True, ordering='-deleted_at',
|
||||||
|
)
|
||||||
|
def render_is_active(self, obj: Association | None = None) -> bool | None:
|
||||||
|
if obj is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return obj.is_active
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(Association, AssociationAdmin)
|
||||||
@@ -11,6 +11,26 @@ class SaveAdmin(admin.ModelAdmin):
|
|||||||
list_display = (
|
list_display = (
|
||||||
'pk', 'key', 'account_uuid', 'created_at', 'render_is_active',
|
'pk', 'key', 'account_uuid', 'created_at', 'render_is_active',
|
||||||
)
|
)
|
||||||
|
search_fields = ('pk', 'account_uuid', 'url')
|
||||||
|
fields = (
|
||||||
|
'pk',
|
||||||
|
'account_uuid',
|
||||||
|
'key',
|
||||||
|
'url',
|
||||||
|
'title',
|
||||||
|
'description',
|
||||||
|
'last_processed_at',
|
||||||
|
'is_netloc_banned',
|
||||||
|
'created_at',
|
||||||
|
'deleted_at',
|
||||||
|
)
|
||||||
|
readonly_fields = (
|
||||||
|
'pk',
|
||||||
|
'account_uuid',
|
||||||
|
'key',
|
||||||
|
'created_at',
|
||||||
|
'deleted_at',
|
||||||
|
)
|
||||||
ordering = ['-created_at']
|
ordering = ['-created_at']
|
||||||
|
|
||||||
def has_delete_permission(self, request, obj=None):
|
def has_delete_permission(self, request, obj=None):
|
||||||
|
|||||||
@@ -100,3 +100,9 @@ def page_includes(request: HttpRequest) -> dict:
|
|||||||
'script': settings.UI_PAGE_SCRIPT_INCLUDES,
|
'script': settings.UI_PAGE_SCRIPT_INCLUDES,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def operator_email(request: HttpRequest) -> dict:
|
||||||
|
return {
|
||||||
|
'OPERATOR_EMAIL': settings.OPERATOR_EMAIL,
|
||||||
|
}
|
||||||
|
|||||||
19
services/backend/hotpocket_backend/apps/ui/dto/rpc.py
Normal file
19
services/backend/hotpocket_backend/apps/ui/dto/rpc.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import pydantic
|
||||||
|
|
||||||
|
|
||||||
|
class SavesCreateOut(pydantic.BaseModel):
|
||||||
|
id: uuid.UUID
|
||||||
|
target_uuid: uuid.UUID
|
||||||
|
url: pydantic.AnyHttpUrl
|
||||||
|
|
||||||
|
def to_rpc(self) -> dict:
|
||||||
|
return {
|
||||||
|
'id': self.id,
|
||||||
|
'target_uuid': self.target_uuid,
|
||||||
|
'url': str(self.url),
|
||||||
|
}
|
||||||
@@ -3,19 +3,28 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from bthlabs_jsonrpc_core import register_method
|
from bthlabs_jsonrpc_core import register_method
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
from hotpocket_backend.apps.core.rpc import wrap_soa_errors
|
from hotpocket_backend.apps.core.rpc import wrap_soa_errors
|
||||||
|
from hotpocket_backend.apps.ui.dto.rpc import SavesCreateOut
|
||||||
from hotpocket_backend.apps.ui.services.workflows import CreateSaveWorkflow
|
from hotpocket_backend.apps.ui.services.workflows import CreateSaveWorkflow
|
||||||
from hotpocket_soa.dto.associations import AssociationOut
|
|
||||||
|
|
||||||
|
|
||||||
@register_method(method='saves.create')
|
@register_method(method='saves.create')
|
||||||
@wrap_soa_errors
|
@wrap_soa_errors
|
||||||
def create(request: HttpRequest, url: str) -> AssociationOut:
|
def create(request: HttpRequest, url: str) -> SavesCreateOut:
|
||||||
association = CreateSaveWorkflow().run_rpc(
|
association = CreateSaveWorkflow().run_rpc(
|
||||||
request=request,
|
request=request,
|
||||||
account=request.user,
|
account=request.user,
|
||||||
url=url,
|
url=url,
|
||||||
)
|
)
|
||||||
|
|
||||||
return association
|
result = SavesCreateOut.model_validate({
|
||||||
|
'id': association.pk,
|
||||||
|
'target_uuid': association.target_uuid,
|
||||||
|
'url': request.build_absolute_uri(reverse(
|
||||||
|
'ui.associations.view', args=(association.pk,),
|
||||||
|
)),
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
|||||||
@@ -45,7 +45,6 @@
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
onLoad = (event) => {
|
onLoad = (event) => {
|
||||||
console.log('HotPocketApp.onLoad()', event);
|
|
||||||
for (let pluginSpec of this.plugins) {
|
for (let pluginSpec of this.plugins) {
|
||||||
if (pluginSpec[1].onLoad) {
|
if (pluginSpec[1].onLoad) {
|
||||||
pluginSpec[1].onLoad(event);
|
pluginSpec[1].onLoad(event);
|
||||||
|
|||||||
@@ -36,6 +36,12 @@
|
|||||||
{% blocktranslate %}Log in with {{ HOTPOCKET_OIDC_DISPLAY_NAME }}{% endblocktranslate %}
|
{% blocktranslate %}Log in with {{ HOTPOCKET_OIDC_DISPLAY_NAME }}{% endblocktranslate %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if OPERATOR_EMAIL %}
|
||||||
|
<p class="my-0 mt-2 text-center">
|
||||||
|
<small>{% blocktranslate %}For support with your account, contact the <a href="mailto:{{ OPERATOR_EMAIL }}">instance operator</a>.{% endblocktranslate %}</small>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% include "ui/ui/partials/uname.html" %}
|
{% include "ui/ui/partials/uname.html" %}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer d-flex align-items-center">
|
<div class="card-footer d-flex align-items-center">
|
||||||
<a href="{{ association.target.url }}" target="_blank" rel="noopener noreferrer"><small>{{ association.target.url|render_url_domain }}</small></a>
|
<a href="{{ association.target.url }}" target="_blank" rel="noopener noreferrer"><small>{{ association.target.url|render_url_domain }} <i class="bi bi-box-arrow-up-right"></i></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 ui-htmx-indicator" role="status">
|
<div class="spinner-border spinner-border-sm ui-htmx-indicator" role="status">
|
||||||
|
|||||||
@@ -130,6 +130,56 @@
|
|||||||
{% translate 'Log out' %}
|
{% translate 'Log out' %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item px-3 d-flex justify-content-center">
|
||||||
|
{% spaceless %}
|
||||||
|
<a
|
||||||
|
class="btn btn-outline-info btn-sm"
|
||||||
|
href="https://apps.apple.com/pl/app/hotpocket-by-bthlabs/id6752321380"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
title="{% translate 'Safari and Share extension for iOS and macOS' %}"
|
||||||
|
>
|
||||||
|
<i class="bi bi-apple"></i>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class="btn btn-outline-info btn-sm ms-1"
|
||||||
|
href="https://chromewebstore.google.com/detail/save-to-hotpocket/mkmoejhhgnadmijpgkkioicjmikkkjbd"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
title="{% translate 'Chrome extension' %}"
|
||||||
|
>
|
||||||
|
<i class="bi bi-browser-chrome"></i>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class="btn btn-outline-info btn-sm ms-1"
|
||||||
|
href="https://addons.mozilla.org/en-GB/firefox/addon/save-to-hotpocket/"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
title="{% translate 'Firefox extension' %}"
|
||||||
|
>
|
||||||
|
<i class="bi bi-browser-firefox"></i>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class="btn btn-outline-info btn-sm ms-1"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#modalPWA"
|
||||||
|
href="#"
|
||||||
|
title="{% translate 'Android PWA' %}"
|
||||||
|
>
|
||||||
|
<i class="bi bi-android2"></i>
|
||||||
|
</a>
|
||||||
|
<div class="vr my-1 ms-1 opacity-75"></div>
|
||||||
|
<a
|
||||||
|
class="btn btn-outline-info btn-sm ms-1"
|
||||||
|
href="https://git.bthlabs.pl/tomekwojcik/hotpocket"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
title="{% translate 'Source code repository' %}"
|
||||||
|
>
|
||||||
|
<i class="bi bi-git"></i>
|
||||||
|
</a>
|
||||||
|
{% endspaceless %}
|
||||||
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'ui.accounts.login' %}">
|
<a class="nav-link" href="{% url 'ui.accounts.login' %}">
|
||||||
@@ -142,6 +192,38 @@
|
|||||||
{% include "ui/ui/partials/uname.html" %}
|
{% include "ui/ui/partials/uname.html" %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if not request.user.is_anonymous %}
|
||||||
|
<div class="modal modal-fade" id="modalPWA" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h1 class="modal-title fs-5" id="modalPWALabel">
|
||||||
|
{% translate 'HotPocket on Android and others' %}
|
||||||
|
</h1>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p class="mb-1">
|
||||||
|
{% blocktranslate %}
|
||||||
|
HotPocket doesn't natively support Android and other systems. However, it's a Progressive Web Application. You can install it from your browser and it'll register itself as share target.
|
||||||
|
{% endblocktranslate %}
|
||||||
|
</p>
|
||||||
|
<p class="mb-0">
|
||||||
|
{% blocktranslate %}
|
||||||
|
This is currently supported on Android and Windows 11 (when installed using Edge).
|
||||||
|
{% endblocktranslate %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">
|
||||||
|
{% translate 'Cool' %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
|
|||||||
@@ -3,11 +3,13 @@
|
|||||||
{% if save.is_youtube_video %}
|
{% if save.is_youtube_video %}
|
||||||
<div class="mb-0 d-flex justify-content-center">
|
<div class="mb-0 d-flex justify-content-center">
|
||||||
<iframe
|
<iframe
|
||||||
allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
allow="accelerometer *; clipboard-write *; encrypted-media *; gyroscope *; picture-in-picture *; web-share *;"
|
||||||
allowfullscreen
|
allowfullscreen
|
||||||
class="ui-youtube-iframe"
|
class="ui-youtube-iframe"
|
||||||
frameborder="0"
|
frameborder="0"
|
||||||
height="200"
|
height="200"
|
||||||
|
referrerpolicy="strict-origin"
|
||||||
|
scrolling="no"
|
||||||
src="{{ save|render_youtube_embed_url }}"
|
src="{{ save|render_youtube_embed_url }}"
|
||||||
title="YouTube video player"
|
title="YouTube video player"
|
||||||
width="320"
|
width="320"
|
||||||
|
|||||||
@@ -27,9 +27,6 @@ def page_not_found(request: HttpRequest,
|
|||||||
exception: Exception,
|
exception: Exception,
|
||||||
template_name: str = ERROR_404_TEMPLATE_NAME,
|
template_name: str = ERROR_404_TEMPLATE_NAME,
|
||||||
) -> HttpResponseNotFound:
|
) -> HttpResponseNotFound:
|
||||||
if exception:
|
|
||||||
LOGGER.error('Exception: %s', exception, exc_info=exception)
|
|
||||||
|
|
||||||
return HttpResponseNotFound(render_to_string(
|
return HttpResponseNotFound(render_to_string(
|
||||||
'ui/errors/page_not_found.html',
|
'ui/errors/page_not_found.html',
|
||||||
context={},
|
context={},
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ TEMPLATES = [
|
|||||||
'hotpocket_backend.apps.ui.context_processors.debug',
|
'hotpocket_backend.apps.ui.context_processors.debug',
|
||||||
'hotpocket_backend.apps.ui.context_processors.version',
|
'hotpocket_backend.apps.ui.context_processors.version',
|
||||||
'hotpocket_backend.apps.ui.context_processors.appearance_settings',
|
'hotpocket_backend.apps.ui.context_processors.appearance_settings',
|
||||||
|
'hotpocket_backend.apps.ui.context_processors.operator_email',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -295,3 +296,5 @@ SAVES_ASSOCIATION_ADAPTER = os.environ.get(
|
|||||||
UPLOADS_PATH = None
|
UPLOADS_PATH = None
|
||||||
|
|
||||||
SITE_SHORT_TITLE = 'HotPocket'
|
SITE_SHORT_TITLE = 'HotPocket'
|
||||||
|
|
||||||
|
OPERATOR_EMAIL = os.environ.get('HOTPOCKET_BACKEND_OPERATOR_EMAIL', None)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ cat <<EOF
|
|||||||
|_|
|
|_|
|
||||||
production
|
production
|
||||||
|
|
||||||
HotPocket v25.11.18 [${HOTPOCKET_BACKEND_IMAGE_ID}] (https://hotpocket.app/)
|
HotPocket v25.12.04 [${HOTPOCKET_BACKEND_IMAGE_ID}] (https://hotpocket.app/)
|
||||||
Copyright 2025-present by BTHLabs. All rights reserved. (https://bthlabs.pl/)
|
Copyright 2025-present by BTHLabs. All rights reserved. (https://bthlabs.pl/)
|
||||||
Licensed under Apache-2.0
|
Licensed under Apache-2.0
|
||||||
EOF
|
EOF
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hotpocket-backend",
|
"name": "hotpocket-backend",
|
||||||
"version": "25.11.18",
|
"version": "25.12.04",
|
||||||
"description": "HotPocket Backend",
|
"description": "HotPocket Backend",
|
||||||
"main": "hotpocket_backend/apps/frontend/src/index.js",
|
"main": "hotpocket_backend/apps/frontend/src/index.js",
|
||||||
"repository": "https://git.bthlabs.pl/tomekwojcik/hotpocket",
|
"repository": "https://git.bthlabs.pl/tomekwojcik/hotpocket",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "hotpocket-backend"
|
name = "hotpocket-backend"
|
||||||
version = "25.11.18"
|
version = "25.12.04"
|
||||||
description = "HotPocket Backend"
|
description = "HotPocket Backend"
|
||||||
authors = ["Tomek Wójcik <contact@bthlabs.pl>"]
|
authors = ["Tomek Wójcik <contact@bthlabs.pl>"]
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ def ci(ctx: Context):
|
|||||||
@task
|
@task
|
||||||
def setup(ctx: Context):
|
def setup(ctx: Context):
|
||||||
ctx.run('python manage.py migrate')
|
ctx.run('python manage.py migrate')
|
||||||
ctx.run('python manage.py create_initial_account hotpocket hotpocketm4st3r')
|
ctx.run('python manage.py create_initial_account -u hotpocket -p hotpocketm4st3r')
|
||||||
|
|
||||||
if WORKSPACE_MODE == WorkspaceMode.METAL:
|
if WORKSPACE_MODE == WorkspaceMode.METAL:
|
||||||
ctx.run('mkdir -p run/uploads')
|
ctx.run('mkdir -p run/uploads')
|
||||||
|
|||||||
@@ -54,8 +54,12 @@ def test_ok(authenticated_client: Client,
|
|||||||
call_result = result.json()
|
call_result = result.json()
|
||||||
assert 'error' not in call_result
|
assert 'error' not in call_result
|
||||||
|
|
||||||
save_pk = uuid.UUID(call_result['result']['target_uuid'])
|
|
||||||
association_pk = uuid.UUID(call_result['result']['id'])
|
association_pk = uuid.UUID(call_result['result']['id'])
|
||||||
|
save_pk = uuid.UUID(call_result['result']['target_uuid'])
|
||||||
|
|
||||||
|
assert call_result['result']['url'].endswith(reverse(
|
||||||
|
'ui.associations.view', args=(association_pk,),
|
||||||
|
))
|
||||||
|
|
||||||
AssociationsTestingService().assert_created(
|
AssociationsTestingService().assert_created(
|
||||||
pk=association_pk,
|
pk=association_pk,
|
||||||
|
|||||||
@@ -1,3 +1,36 @@
|
|||||||
# HotPocket by BTHLabs
|
# HotPocket by BTHLabs
|
||||||
|
|
||||||
This repository contains the _HotPocket Extension_ project.
|
This repository contains the _HotPocket Extension_ project.
|
||||||
|
|
||||||
|
## Development environment setup
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
* macOS (18 or newer) or modern Linux (including WSL2).
|
||||||
|
* nodejs v22.14 or later.
|
||||||
|
* yarnpkg v1.22.22 or newer.
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
1. `yarn install`
|
||||||
|
|
||||||
|
## Building browser-specific extensions
|
||||||
|
|
||||||
|
To build a browser-specific extension, use the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ yarn build:<target>
|
||||||
|
```
|
||||||
|
|
||||||
|
Where `<target>` is the name of the browser. The currently supported targets
|
||||||
|
are: `safari`, `firefox`, `chrome`. The build result will be placed in
|
||||||
|
`dist/<target>-production` for Firefox and Chrome extensions. Safari
|
||||||
|
extension will be built in the `apple` service tree.
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
_HotPocket_ is developed by [BTHLabs](https://www.bthlabs.pl/).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
_HotPocket_ is licensed under the Apache 2.0 License.
|
||||||
|
|||||||
@@ -30,5 +30,9 @@
|
|||||||
"content_popup_content_error_message": {
|
"content_popup_content_error_message": {
|
||||||
"message": "HotPocket couldn't complete this operation.",
|
"message": "HotPocket couldn't complete this operation.",
|
||||||
"description": "Title of the error content popup."
|
"description": "Title of the error content popup."
|
||||||
|
},
|
||||||
|
"content_popup_content_view_button_message": {
|
||||||
|
"message": "View in HotPocket",
|
||||||
|
"description": "Title of the view link button"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hotpocket-extension",
|
"name": "hotpocket-extension",
|
||||||
"version": "25.11.18",
|
"version": "25.11.26",
|
||||||
"description": "HotPocket Extension",
|
"description": "HotPocket Extension",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"repository": "https://git.bthlabs.pl/tomekwojcik/hotpocket",
|
"repository": "https://git.bthlabs.pl/tomekwojcik/hotpocket",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "hotpocket-extension"
|
name = "hotpocket-extension"
|
||||||
version = "25.11.18"
|
version = "25.11.26"
|
||||||
description = "HotPocket Extension"
|
description = "HotPocket Extension"
|
||||||
authors = ["Tomek Wójcik <contact@bthlabs.pl>"]
|
authors = ["Tomek Wójcik <contact@bthlabs.pl>"]
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
|
|||||||
@@ -117,10 +117,10 @@ const doSave = async (accessToken, tab) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (error !== null) {
|
if (error !== null) {
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
const doCreateAndStoreAccessToken = async (authKey) => {
|
const doCreateAndStoreAccessToken = async (authKey) => {
|
||||||
|
|||||||
@@ -19,18 +19,32 @@ class Popup {
|
|||||||
this.timeout = null;
|
this.timeout = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
setContent = (content) => {
|
replaceInnerHTML = (element, content) => {
|
||||||
|
for (let child of element.childNodes) {
|
||||||
|
element.removeChild(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
element.insertAdjacentHTML('beforeend', content);
|
||||||
|
};
|
||||||
|
setContent = (content, saveUrl) => {
|
||||||
const shadow = this.container.shadowRoot;
|
const shadow = this.container.shadowRoot;
|
||||||
|
|
||||||
const body = shadow.querySelector('.hotpocket-extension-popup-body');
|
const body = shadow.querySelector('.hotpocket-extension-popup-body');
|
||||||
body.innerHTML = content;
|
this.replaceInnerHTML(body, content);
|
||||||
|
|
||||||
const i18nElements = shadow.querySelectorAll('[data-message]');
|
const i18nElements = shadow.querySelectorAll('[data-message]');
|
||||||
for (let i18nElement of i18nElements) {
|
for (let i18nElement of i18nElements) {
|
||||||
i18nElement.innerHTML = HotPocketExtension.api.i18n.getMessage(
|
i18nElement.textContent = HotPocketExtension.api.i18n.getMessage(
|
||||||
i18nElement.dataset.message,
|
i18nElement.dataset.message,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (saveUrl) {
|
||||||
|
const viewLink = shadow.querySelector('.hotpocket-extension-popup-view');
|
||||||
|
if (viewLink) {
|
||||||
|
viewLink.href = saveUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
close = () => {
|
close = () => {
|
||||||
this.clearCloseTimeout();
|
this.clearCloseTimeout();
|
||||||
@@ -40,7 +54,7 @@ class Popup {
|
|||||||
this.container = null;
|
this.container = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
show = (content) => {
|
show = (content, saveUrl) => {
|
||||||
this.close();
|
this.close();
|
||||||
|
|
||||||
this.container = document.createElement('div');
|
this.container = document.createElement('div');
|
||||||
@@ -48,9 +62,9 @@ class Popup {
|
|||||||
this.container.hotPocketExtensionPopup = this;
|
this.container.hotPocketExtensionPopup = this;
|
||||||
|
|
||||||
const shadow = this.container.attachShadow({mode: 'open'});
|
const shadow = this.container.attachShadow({mode: 'open'});
|
||||||
shadow.innerHTML = POPUP;
|
shadow.setHTMLUnsafe(POPUP);
|
||||||
|
|
||||||
this.setContent(content);
|
this.setContent(content, saveUrl);
|
||||||
|
|
||||||
const closeElements = shadow.querySelectorAll('.hotpocket-extension-popup-close');
|
const closeElements = shadow.querySelectorAll('.hotpocket-extension-popup-close');
|
||||||
for (const closeElement of closeElements) {
|
for (const closeElement of closeElements) {
|
||||||
@@ -59,8 +73,8 @@ class Popup {
|
|||||||
|
|
||||||
document.body.appendChild(this.container);
|
document.body.appendChild(this.container);
|
||||||
};
|
};
|
||||||
update = (content) => {
|
update = (content, saveUrl) => {
|
||||||
this.setContent(content);
|
this.setContent(content, saveUrl);
|
||||||
};
|
};
|
||||||
onCloseClick = (event) => {
|
onCloseClick = (event) => {
|
||||||
this.close();
|
this.close();
|
||||||
@@ -79,16 +93,16 @@ const doHandleBrowserActionClickedMessage = (message) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const doHandleSaveMessage = (message) => {
|
const doHandleSaveMessage = (message) => {
|
||||||
let content = POPUP_CONTENT_ERROR;
|
let content = POPUP_CONTENT_SUCCESS;
|
||||||
if (message.result === true) {
|
if (message.result === null) {
|
||||||
content = POPUP_CONTENT_SUCCESS;
|
content = POPUP_CONTENT_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentPopup === null) {
|
if (currentPopup === null) {
|
||||||
currentPopup = new Popup();
|
currentPopup = new Popup();
|
||||||
currentPopup.show(content);
|
currentPopup.show(content);
|
||||||
} else {
|
} else {
|
||||||
currentPopup.update(content);
|
currentPopup.update(content, (message.result || {}).url);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -94,6 +94,26 @@
|
|||||||
opacity: 0.83;
|
opacity: 0.83;
|
||||||
transform: rotate(60deg);
|
transform: rotate(60deg);
|
||||||
}
|
}
|
||||||
|
.hotpocket-extension-popup-view {
|
||||||
|
background-color: #1CBAED;
|
||||||
|
border: 1px solid #1CBAED;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
color: white;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.25;
|
||||||
|
margin-top: 0.25rem !important;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.hotpocket-extension-popup-view:hover {
|
||||||
|
background-color: #189ec9;
|
||||||
|
border-color: #1695be;
|
||||||
|
}
|
||||||
|
.hotpocket-extension-popup-view:active {
|
||||||
|
background-color: #1695be;
|
||||||
|
border-color: #158cb2;
|
||||||
|
}
|
||||||
@keyframes hotpocket-extension-popup-loader-animation {
|
@keyframes hotpocket-extension-popup-loader-animation {
|
||||||
100% {transform: rotate(1turn)}
|
100% {transform: rotate(1turn)}
|
||||||
}
|
}
|
||||||
@@ -104,6 +124,6 @@
|
|||||||
<strong>HotPocket by BTHLabs</strong>
|
<strong>HotPocket by BTHLabs</strong>
|
||||||
<a class="hotpocket-extension-popup-close">×</a>
|
<a class="hotpocket-extension-popup-close">×</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="hotpocket-extension-popup-body">
|
<div class="hotpocket-extension-popup-body hotpocket-extension-popup-message-success">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,4 +2,13 @@
|
|||||||
<strong data-message="content_popup_content_success_title"></strong>
|
<strong data-message="content_popup_content_success_title"></strong>
|
||||||
<br>
|
<br>
|
||||||
<span data-message="content_popup_content_success_message"></span>
|
<span data-message="content_popup_content_success_message"></span>
|
||||||
|
<br>
|
||||||
|
<a
|
||||||
|
class="hotpocket-extension-popup-view"
|
||||||
|
data-message="content_popup_content_view_button_message"
|
||||||
|
href="#"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"name": "__MSG_extension_name__",
|
"name": "__MSG_extension_name__",
|
||||||
"description": "__MSG_extension_description__",
|
"description": "__MSG_extension_description__",
|
||||||
"version": "25.11.18",
|
"version": "25.11.26",
|
||||||
"icons": {
|
"icons": {
|
||||||
"16": "images/icon-16.png",
|
"16": "images/icon-16.png",
|
||||||
"32": "images/icon-32.png",
|
"32": "images/icon-32.png",
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
},
|
},
|
||||||
"browser_specific_settings": {
|
"browser_specific_settings": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
"id": "@Extension.HotPocket.BTHLabs",
|
"id": "@Production.Extension.HotPocket.BTHLabs",
|
||||||
"strict_min_version": "142.0",
|
"strict_min_version": "142.0",
|
||||||
"data_collection_permissions": {
|
"data_collection_permissions": {
|
||||||
"required": [
|
"required": [
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
|
||||||
import os
|
import os
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
@@ -14,7 +13,10 @@ import werkzeug
|
|||||||
import werkzeug.routing
|
import werkzeug.routing
|
||||||
|
|
||||||
from hotpocket_workspace_tools import get_workspace_mode
|
from hotpocket_workspace_tools import get_workspace_mode
|
||||||
from hotpocket_workspace_tools.tasks import * # noqa: F401,F403
|
from hotpocket_workspace_tools.tasks import ( # noqa: F401
|
||||||
|
bump_version,
|
||||||
|
get_version,
|
||||||
|
)
|
||||||
|
|
||||||
WORKSPACE_MODE = get_workspace_mode()
|
WORKSPACE_MODE = get_workspace_mode()
|
||||||
|
|
||||||
@@ -198,26 +200,54 @@ def build_safari(ctx: Context):
|
|||||||
@task(pre=[clean])
|
@task(pre=[clean])
|
||||||
def build_chrome(ctx: Context):
|
def build_chrome(ctx: Context):
|
||||||
ctx.run('yarn build:chrome')
|
ctx.run('yarn build:chrome')
|
||||||
ctx.run(' '.join([
|
|
||||||
r'/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome',
|
with ctx.cd('dist/chrome-production'):
|
||||||
'--pack-extension=dist/chrome-production/',
|
current_version = get_version(ctx)
|
||||||
'--pack-extension-key=secrets/chrome.pem',
|
|
||||||
]))
|
ctx.run(' '.join([
|
||||||
|
'zip',
|
||||||
|
'-r',
|
||||||
|
f'../chrome-production-{current_version}.zip',
|
||||||
|
'.',
|
||||||
|
]))
|
||||||
|
|
||||||
|
|
||||||
@task(pre=[clean])
|
@task(pre=[clean])
|
||||||
def build_firefox(ctx: Context):
|
def build_firefox(ctx: Context):
|
||||||
ctx.run('yarn build:firefox')
|
ctx.run('yarn build:firefox')
|
||||||
|
|
||||||
firefox_secrets = None
|
|
||||||
with open('secrets/firefox.json', 'r', encoding='utf-8') as firefox_secrets_f:
|
|
||||||
firefox_secrets = json.load(firefox_secrets_f)
|
|
||||||
|
|
||||||
with ctx.cd('dist/firefox-production'):
|
with ctx.cd('dist/firefox-production'):
|
||||||
ctx.run(' '.join([
|
ctx.run(' '.join([
|
||||||
'web-ext',
|
'web-ext',
|
||||||
'sign',
|
'lint',
|
||||||
'--channel=unlisted',
|
]))
|
||||||
f'--api-key={firefox_secrets["api_key"]}',
|
|
||||||
f'--api-secret={firefox_secrets["api_secret"]}',
|
current_version = get_version(ctx)
|
||||||
|
|
||||||
|
ctx.run(' '.join([
|
||||||
|
'zip',
|
||||||
|
'-r',
|
||||||
|
f'../firefox-production-{current_version}.zip',
|
||||||
|
'.',
|
||||||
|
]))
|
||||||
|
|
||||||
|
|
||||||
|
@task
|
||||||
|
def build_firefox_source(ctx: Context):
|
||||||
|
# AMO requires source bundle to be uploaded alongside the built version.
|
||||||
|
ctx.run('rm -rf dist/firefox-source')
|
||||||
|
ctx.run('mkdir -p dist/firefox-source dist/firefox-source/assets dist/firefox-source/src')
|
||||||
|
|
||||||
|
ctx.run('rsync -arv assets/ dist/firefox-source/assets/')
|
||||||
|
ctx.run('rsync -arv src/ dist/firefox-source/src/')
|
||||||
|
ctx.run('rsync -arv eslint.config.js package.json README.md rollup.config.js yarn.lock dist/firefox-source/')
|
||||||
|
|
||||||
|
with ctx.cd('dist/firefox-source'):
|
||||||
|
current_version = get_version(ctx)
|
||||||
|
|
||||||
|
ctx.run(' '.join([
|
||||||
|
'zip',
|
||||||
|
'-r',
|
||||||
|
f'../firefox-source-{current_version}.zip',
|
||||||
|
'.',
|
||||||
]))
|
]))
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
from .utils import bump_version # noqa: F401
|
from .utils import bump_version, get_version # noqa: F401
|
||||||
|
|||||||
@@ -4,12 +4,16 @@ from __future__ import annotations
|
|||||||
from invoke import Context, task
|
from invoke import Context, task
|
||||||
|
|
||||||
|
|
||||||
|
def get_version(ctx: Context) -> str:
|
||||||
|
result = ctx.run('poetry version -s --no-ansi', hide='out')
|
||||||
|
assert result is not None, 'Hm?'
|
||||||
|
|
||||||
|
return result.stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
@task
|
@task
|
||||||
def bump_version(ctx: Context, next_version: str, build: str | None = None):
|
def bump_version(ctx: Context, next_version: str, build: str | None = None):
|
||||||
current_version_result = ctx.run('poetry version -s --no-ansi', hide='out')
|
current_version = get_version(ctx)
|
||||||
assert current_version_result is not None, 'Hm?'
|
|
||||||
|
|
||||||
current_version = current_version_result.stdout.strip()
|
|
||||||
|
|
||||||
print(f'Bumping version: `{current_version}` -> `{next_version}`')
|
print(f'Bumping version: `{current_version}` -> `{next_version}`')
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user