Files
upstream-aw-server/aw_server/server.py
upstream-mirror 07fb811788
Some checks failed
Build / Test on ubuntu-latest (push) Successful in 3m6s
Lint / lint (push) Successful in 22s
Build / typecheck (ubuntu-latest, 3.9) (push) Successful in 1m49s
Lint / format (push) Successful in 42s
Build / Test on macOS-latest (push) Has been cancelled
Build / Test on windows-latest (push) Has been cancelled
CodeQL / Analyze (python) (push) Failing after 1m31s
snapshot of https://github.com/ActivityWatch/aw-server.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 00:20:00 +05:00

152 lines
4.4 KiB
Python

import logging
import os
from datetime import datetime, timedelta
from typing import Dict, List
import aw_datastore
import flask.json.provider
from aw_datastore import Datastore
from flask import (
Blueprint,
Flask,
current_app,
send_from_directory,
)
from flask_cors import CORS
from . import extension_cors, rest
from .api import ServerAPI
from .custom_static import get_custom_static_blueprint
from .log import FlaskLogHandler
logger = logging.getLogger(__name__)
app_folder = os.path.dirname(os.path.abspath(__file__))
static_folder = os.path.join(app_folder, "static")
root = Blueprint("root", __name__, url_prefix="/")
class AWFlask(Flask):
def __init__(
self,
host: str,
testing: bool,
storage_method=None,
cors_origins=[],
custom_static=dict(),
static_folder=static_folder,
static_url_path="",
):
name = "aw-server"
self.json_provider_class = CustomJSONProvider
# only prettyprint JSON if testing (due to perf)
self.json_provider_class.compact = not testing
# Initialize Flask
Flask.__init__(
self,
name,
static_folder=static_folder,
static_url_path=static_url_path,
)
self.config["HOST"] = host # needed for host-header check
with self.app_context():
_config_cors(cors_origins, testing)
# Initialize datastore and API
if storage_method is None:
storage_method = aw_datastore.get_storage_methods()["memory"]
db = Datastore(storage_method, testing=testing)
self.api = ServerAPI(db=db, testing=testing)
self.register_blueprint(root)
self.register_blueprint(rest.blueprint)
self.register_blueprint(get_custom_static_blueprint(custom_static))
class CustomJSONProvider(flask.json.provider.DefaultJSONProvider):
# encoding/decoding of datetime as iso8601 strings
# encoding of timedelta as second floats
def default(self, obj, *args, **kwargs):
try:
if isinstance(obj, datetime):
return obj.isoformat()
if isinstance(obj, timedelta):
return obj.total_seconds()
except TypeError:
pass
return super().default(obj)
@root.route("/")
def static_root():
return current_app.send_static_file("index.html")
@root.route("/css/<path:path>")
def static_css(path):
return send_from_directory(static_folder + "/css", path)
@root.route("/js/<path:path>")
def static_js(path):
return send_from_directory(static_folder + "/js", path)
def _config_cors(cors_origins: List[str], testing: bool):
if cors_origins:
logger.warning(
"Running with additional allowed CORS origins specified through config "
"or CLI argument (could be a security risk): {}".format(cors_origins)
)
if testing:
# Used for development of aw-webui
cors_origins.append("http://127.0.0.1:27180/*")
# Capture user-configured origins before appending the built-in wildcard.
# extension_cors uses this list to exempt explicit opt-ins from scope narrowing.
user_origins = list(cors_origins)
# TODO: This could probably be more specific
# See https://github.com/ActivityWatch/aw-server/pull/43#issuecomment-386888769
cors_origins.append("moz-extension://*")
# See: https://flask-cors.readthedocs.org/en/latest/
CORS(current_app, resources={r"/api/*": {"origins": cors_origins}})
# Narrow the moz-extension wildcard to only the endpoints aw-watcher-web needs.
# See aw_server/extension_cors.py and ActivityWatch/aw-server-rust#637.
extension_cors.register(current_app._get_current_object(), user_origins)
# Only to be called from aw_server.main function!
def _start(
storage_method,
host: str,
port: int,
testing: bool = False,
cors_origins: List[str] = [],
custom_static: Dict[str, str] = dict(),
):
app = AWFlask(
host,
testing=testing,
storage_method=storage_method,
cors_origins=cors_origins,
custom_static=custom_static,
)
try:
app.run(
debug=testing,
host=host,
port=port,
request_handler=FlaskLogHandler,
use_reloader=False,
threaded=True,
)
except OSError as e:
logger.exception(e)
raise e