Compare commits

..

2 Commits

Author SHA1 Message Date
Andrey Antukh
18f9158d43 Add better approach for error handling to obj/reify 2026-02-13 12:17:37 +01:00
Andrey Antukh
43af62e5bc Make the obj/proxy object do not extend js/Object directly 2026-02-13 08:21:37 +01:00
43 changed files with 623 additions and 751 deletions

View File

@@ -80,7 +80,7 @@ jobs:
- name: "Build package for ${{ inputs.plugin_name }}-plugin" - name: "Build package for ${{ inputs.plugin_name }}-plugin"
working-directory: plugins working-directory: plugins
shell: bash shell: bash
run: pnpm --filter ${{ inputs.plugin_name }}-plugin build run: npx nx build ${{ inputs.plugin_name }}-plugin
- name: Select Worker name - name: Select Worker name
run: | run: |

View File

@@ -78,7 +78,7 @@ jobs:
- name: Build styles - name: Build styles
working-directory: plugins working-directory: plugins
shell: bash shell: bash
run: pnpm run build:styles-example run: npx nx run example-styles:build
- name: Select Worker name - name: Select Worker name
run: | run: |

View File

@@ -12,7 +12,6 @@ penpot - error list
<a class="{% if version = 3 %}strong{% endif %}" href="?version=3">[BACKEND ERRORS]</a> <a class="{% if version = 3 %}strong{% endif %}" href="?version=3">[BACKEND ERRORS]</a>
<a class="{% if version = 4 %}strong{% endif %}" href="?version=4">[FRONTEND ERRORS]</a> <a class="{% if version = 4 %}strong{% endif %}" href="?version=4">[FRONTEND ERRORS]</a>
<a class="{% if version = 5 %}strong{% endif %}" href="?version=5">[RLIMIT REPORTS]</a>
</div> </div>
</nav> </nav>
<main class="horizontal-list"> <main class="horizontal-list">

View File

@@ -1,40 +0,0 @@
{% extends "app/templates/base.tmpl" %}
{% block title %}
Report: {{hint|abbreviate:150}} - {{id}} - Penpot Rate Limit Report
{% endblock %}
{% block content %}
<nav>
<div>[<a href="/dbg/error?version={{version}}">⮜</a>]</div>
<div>[<a href="#head">head</a>]</div>
<div>[<a href="#context">context</a>]</div>
<div>[<a href="#result">result</a>]</div>
</nav>
<main>
<div class="table">
<div class="table-row multiline">
<div id="head" class="table-key">HEAD:</div>
<div class="table-val">
<h1><span class="not-important">Hint:</span> <br/> {{hint}}</h1>
<h2><span class="not-important">Reported at:</span> <br/> {{created-at}}</h2>
<h2><span class="not-important">Report ID:</span> <br/> {{id}}</h2>
</div>
</div>
<div class="table-row multiline">
<div id="context" class="table-key">CONTEXT: </div>
<div class="table-val">
<pre>{{context}}</pre>
</div>
</div>
<div class="table-row multiline">
<div id="result" class="table-key">RESULT: </div>
<div class="table-val">
<pre>{{result}}</pre>
</div>
</div>
</div>
</main>
{% endblock %}

View File

@@ -3,9 +3,9 @@
{:default {:default
[[:default :window "200000/h"]] [[:default :window "200000/h"]]
;; #{:main/get-teams} ;; #{:command/get-teams}
;; [[:burst :bucket "5/5/5s"]] ;; [[:burst :bucket "5/5/5s"]]
;; #{:main/get-profile} ;; #{:command/get-profile}
;; [[:burst :bucket "60/60/1m"]] ;; [[:burst :bucket "60/60/1m"]]
} }

View File

@@ -240,13 +240,6 @@
(tmpl/render (-> content (tmpl/render (-> content
(assoc :id id) (assoc :id id)
(assoc :version 4) (assoc :version 4)
(assoc :created-at (ct/format-inst created-at :rfc1123))))))
(render-template-v5 [{:keys [content id created-at]}]
(-> (io/resource "app/templates/error-report.v5.tmpl")
(tmpl/render (-> content
(assoc :id id)
(assoc :version 5)
(assoc :created-at (ct/format-inst created-at :rfc1123))))))] (assoc :created-at (ct/format-inst created-at :rfc1123))))))]
(if-let [report (get-report request)] (if-let [report (get-report request)]
@@ -254,8 +247,7 @@
1 (render-template-v1 report) 1 (render-template-v1 report)
2 (render-template-v2 report) 2 (render-template-v2 report)
3 (render-template-v3 report) 3 (render-template-v3 report)
4 (render-template-v4 report) 4 (render-template-v4 report))]
5 (render-template-v5 report))]
{::yres/status 200 {::yres/status 200
::yres/body result ::yres/body result
::yres/headers {"content-type" "text/html; charset=utf-8" ::yres/headers {"content-type" "text/html; charset=utf-8"

View File

@@ -213,14 +213,14 @@
(assoc "access-control-allow-origin" origin) (assoc "access-control-allow-origin" origin)
(assoc "access-control-allow-methods" "GET,POST,DELETE,OPTIONS,PUT,HEAD,PATCH") (assoc "access-control-allow-methods" "GET,POST,DELETE,OPTIONS,PUT,HEAD,PATCH")
(assoc "access-control-allow-credentials" "true") (assoc "access-control-allow-credentials" "true")
(assoc "access-control-expose-headers" "content-type, set-cookie") (assoc "access-control-expose-headers" "x-requested-with, content-type, cookie")
(assoc "access-control-allow-headers" "x-frontend-version, x-client, x-requested-width, content-type, accept, cookie"))) (assoc "access-control-allow-headers" "x-frontend-version, content-type, accept, x-requested-width")))
(defn wrap-cors (defn wrap-cors
[handler] [handler]
(fn [request] (fn [request]
(let [response (if (= (yreq/method request) :options) (let [response (if (= (yreq/method request) :options)
{::yres/status 204} {::yres/status 200}
(handler request)) (handler request))
origin (yreq/get-header request "origin")] origin (yreq/get-header request "origin")]
(update response ::yres/headers with-cors-headers origin)))) (update response ::yres/headers with-cors-headers origin))))

View File

@@ -151,22 +151,20 @@
uuid/zero) uuid/zero)
props (-> (or (::replace-props resultm) props (-> (or (::replace-props resultm)
(merge params (::props resultm))) (-> params
(merge (::props resultm))
(dissoc :profile-id)
(dissoc :type)))
(clean-props)) (clean-props))
context (merge (::context resultm) context (merge (::context resultm)
(prepare-context-from-request request)) (prepare-context-from-request request))
ip-addr (inet/parse-request request) ip-addr (inet/parse-request request)]
module (get cfg ::rpc/module)]
{::type (or (::type resultm) {::type (or (::type resultm)
(::rpc/type cfg)) (::rpc/type cfg))
::name (or (::name resultm) ::name (or (::name resultm)
(let [sname (::sv/name mdata)] (::sv/name mdata))
(if (not= module "main")
(str module "-" sname)
sname)))
::profile-id profile-id ::profile-id profile-id
::ip-addr ip-addr ::ip-addr ip-addr
::props props ::props props

View File

@@ -15,7 +15,6 @@
[app.config :as cf] [app.config :as cf]
[app.db :as db] [app.db :as db]
[app.loggers.audit :as audit] [app.loggers.audit :as audit]
[app.rpc.rlimit :as-alias rlimit]
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
[integrant.core :as ig] [integrant.core :as ig]
[promesa.exec :as px] [promesa.exec :as px]
@@ -42,7 +41,7 @@
(or (instance? java.util.concurrent.CompletionException cause) (or (instance? java.util.concurrent.CompletionException cause)
(instance? java.util.concurrent.ExecutionException cause))) (instance? java.util.concurrent.ExecutionException cause)))
(defn- log-record->report (defn record->report
[{:keys [::l/context ::l/message ::l/props ::l/logger ::l/level ::l/cause] :as record}] [{:keys [::l/context ::l/message ::l/props ::l/logger ::l/level ::l/cause] :as record}]
(assert (l/valid-record? record) "expectd valid log record") (assert (l/valid-record? record) "expectd valid log record")
(let [data (if (concurrent-exception? cause) (let [data (if (concurrent-exception? cause)
@@ -87,16 +86,16 @@
[{:keys [::db/pool]} {:keys [::l/id] :as record}] [{:keys [::db/pool]} {:keys [::l/id] :as record}]
(try (try
(let [uri (cf/get :public-uri) (let [uri (cf/get :public-uri)
report (-> record log-record->report d/without-nils)] report (-> record record->report d/without-nils)]
(l/dbg :hint "registering error on database" (l/dbg :hint "registering error on database"
:id (str id) :id id
:src "logging" :src "logging"
:uri (str uri "/dbg/error/" id)) :uri (str uri "/dbg/error/" id))
(persist-on-database! pool id 3 report)) (persist-on-database! pool id 3 report))
(catch Throwable cause (catch Throwable cause
(l/warn :hint "unexpected exception on database error logger" :cause cause)))) (l/warn :hint "unexpected exception on database error logger" :cause cause))))
(defn- audit-event->report (defn- event->report
[{:keys [::audit/context ::audit/props ::audit/ip-addr] :as record}] [{:keys [::audit/context ::audit/props ::audit/ip-addr] :as record}]
(let [context (let [context
(reduce-kv (fn [context k v] (reduce-kv (fn [context k v]
@@ -126,51 +125,15 @@
[{:keys [::db/pool]} {:keys [::audit/id] :as event}] [{:keys [::db/pool]} {:keys [::audit/id] :as event}]
(try (try
(let [uri (cf/get :public-uri) (let [uri (cf/get :public-uri)
report (-> event audit-event->report d/without-nils)] report (-> event event->report d/without-nils)]
(l/dbg :hint "registering error on database" (l/dbg :hint "registering error on database"
:id (str id) :id id
:src "audit-log" :src "audit-log"
:uri (str uri "/dbg/error/" id)) :uri (str uri "/dbg/error/" id))
(persist-on-database! pool id 4 report)) (persist-on-database! pool id 4 report))
(catch Throwable cause (catch Throwable cause
(l/warn :hint "unexpected exception on database error logger" :cause cause)))) (l/warn :hint "unexpected exception on database error logger" :cause cause))))
(defn- rlimit-event->report
[event]
(let [context
(-> {}
(assoc :rlimit/uid (::rlimit/uid event))
(assoc :rlimit/method (::rlimit/method event))
(assoc :backend/tenant (cf/get :tenant))
(assoc :backend/host (cf/get :host))
(assoc :backend/public-uri (str (cf/get :public-uri)))
(assoc :backend/version (:full cf/version)))
result
(->> (::rlimit/results event)
(mapv (fn [result]
(-> (into (sorted-map) result)
(dissoc ::rlimit/method)))))]
{:hint (str "Rate Limit Rejection: " (::rlimit/method event) " for " (::rlimit/uid event))
:context (-> (into (sorted-map) context)
(pp/pprint-str :length 50))
:result (pp/pprint-str result :length 50)}))
(defn- handle-rlimit-event
"Convert the log record into a report object and persist it on the database"
[{:keys [::db/pool]} {:keys [::rlimit/id] :as event}]
(try
(let [uri (cf/get :public-uri)
report (-> event rlimit-event->report d/without-nils)]
(l/dbg :hint "registering rate limit rejection"
:id (str id)
:src "rlimit"
:uri (str uri "/dbg/error/" id))
(persist-on-database! pool id 5 report))
(catch Throwable cause
(l/warn :hint "unexpected exception on database error logger" :cause cause))))
(defmethod ig/assert-key ::reporter (defmethod ig/assert-key ::reporter
[_ params] [_ params]
(assert (db/pool? (::db/pool params)) "expect valid database pool")) (assert (db/pool? (::db/pool params)) "expect valid database pool"))
@@ -191,9 +154,6 @@
(::audit/id item) (::audit/id item)
(handle-audit-event cfg item) (handle-audit-event cfg item)
(::rlimit/id item)
(handle-rlimit-event cfg item)
:else :else
(l/warn :hint "received unexpected item" :item item)) (l/warn :hint "received unexpected item" :item item))

View File

@@ -9,12 +9,10 @@
(:require (:require
[app.common.exceptions :as ex] [app.common.exceptions :as ex]
[app.common.logging :as l] [app.common.logging :as l]
[app.common.pprint :as pp]
[app.common.uri :as u] [app.common.uri :as u]
[app.config :as cf] [app.config :as cf]
[app.http.client :as http] [app.http.client :as http]
[app.loggers.audit :as audit] [app.loggers.audit :as audit]
[app.rpc.rlimit :as-alias rlimit]
[app.util.json :as json] [app.util.json :as json]
[integrant.core :as ig] [integrant.core :as ig]
[promesa.exec :as px] [promesa.exec :as px]
@@ -24,28 +22,21 @@
(defn- send-mattermost-notification! (defn- send-mattermost-notification!
[cfg {:keys [id] :as report}] [cfg {:keys [id] :as report}]
(let [type (get report :type)
text (str "#" type " | " (get report :hint) "\n"
(when id
(str (u/join (cf/get :public-uri) "/dbg/error/" id) " "))
(let [url (u/join (cf/get :public-uri) "/dbg/error/" id)
text (str "Exception: " url " "
(when-let [pid (:profile-id report)] (when-let [pid (:profile-id report)]
(if (uuid? pid) (str "(pid: #uuid-" pid ")"))
(str "(pid: #uuid-" pid ")")
(str "(pid: #ip-" pid ")")))
"\n" "\n"
"- host: #" (:host report) "\n" "- host: #" (:host report) "\n"
"- tenant: #" (:tenant report) "\n" "- tenant: #" (:tenant report) "\n"
"- origin: #" (:origin report) "\n" "- origin: #" (:origin report) "\n"
(when-let [href (get report :href)] "- href: `" (:href report) "`\n"
(str "- href: `" href "`\n")) "- frontend-version: `" (:frontend-version report) "`\n"
(when-let [version (get report :frontend-version)] "- backend-version: `" (:backend-version report) "`\n"
(str "- frontend-version: `" version "`\n"))
(when-let [version (get report :backend-version)]
(str "- backend-version: `" version "`\n"))
"\n" "\n"
(when-let [info (:info report)]
(str "```\n" info "```"))
(when-let [trace (:trace report)] (when-let [trace (:trace report)]
(str "```\n" (str "```\n"
"Trace:\n" "Trace:\n"
@@ -63,15 +54,13 @@
(l/warn :hint "error on sending data" (l/warn :hint "error on sending data"
:response (pr-str resp))))) :response (pr-str resp)))))
(defn- log-record->report (defn- record->report
[{:keys [::l/context ::l/id ::l/cause ::l/message] :as record}] [{:keys [::l/context ::l/id ::l/cause] :as record}]
(assert (l/valid-record? record) "expectd valid log record") (assert (l/valid-record? record) "expectd valid log record")
(let [public-uri (cf/get :public-uri)] (let [public-uri (cf/get :public-uri)]
{:id id {:id id
:type "exception"
:origin "logging" :origin "logging"
:hint (or (some-> cause ex-message) @message)
:tenant (cf/get :tenant) :tenant (cf/get :tenant)
:host (cf/get :host) :host (cf/get :host)
:backend-version (:full cf/version) :backend-version (:full cf/version)
@@ -85,9 +74,7 @@
(defn- audit-event->report (defn- audit-event->report
[{:keys [::audit/context ::audit/props ::audit/id] :as event}] [{:keys [::audit/context ::audit/props ::audit/id] :as event}]
{:id id {:id id
:type "exception"
:origin "audit-log" :origin "audit-log"
:hint (get props :hint)
:tenant (cf/get :tenant) :tenant (cf/get :tenant)
:host (cf/get :host) :host (cf/get :host)
:backend-version (:full cf/version) :backend-version (:full cf/version)
@@ -95,35 +82,18 @@
:profile-id (:audit/profile-id event) :profile-id (:audit/profile-id event)
:href (get props :href)}) :href (get props :href)})
(defn- rlimit-event->report (defn- handle-log-record
[event] [cfg record]
{:id (::rlimit/id event)
:type "notification"
:origin "rlimit"
:hint (str "rlimit reject of "
(::rlimit/method event)
" for "
(::rlimit/uid event))
:tenant (cf/get :tenant)
:host (cf/get :host)
:backend-version (:full cf/version)
:profile-id (::rlimit/profile-id event)
:info (with-out-str
(println "Rejected by:")
(println "------------")
(println "Method: " (::rlimit/method event))
(println "Limit Name: " (::rlimit/name event))
(println "Limit Strategy:" (::rlimit/strategy event))
(println)
(println "Results & Config:")
(println "-----------------")
(doseq [result (::rlimit/results event)]
(pp/pprint (into (sorted-map) result))))})
(defn- handle-event
[cfg event event->report]
(try (try
(let [report (event->report event)] (let [report (record->report record)]
(send-mattermost-notification! cfg report))
(catch Throwable cause
(l/warn :hint "unhandled error" :cause cause))))
(defn- handle-audit-event
[cfg record]
(try
(let [report (audit-event->report record)]
(send-mattermost-notification! cfg report)) (send-mattermost-notification! cfg report))
(catch Throwable cause (catch Throwable cause
(l/warn :hint "unhandled error" :cause cause)))) (l/warn :hint "unhandled error" :cause cause))))
@@ -146,13 +116,10 @@
(when @enabled (when @enabled
(cond (cond
(::l/id item) (::l/id item)
(handle-event cfg item log-record->report) (handle-log-record cfg item)
(::audit/id item) (::audit/id item)
(handle-event cfg item audit-event->report) (handle-audit-event cfg item)
(::rlimit/id item)
(handle-event cfg item rlimit-event->report)
:else :else
(l/warn :hint "received unexpected item" :item item))) (l/warn :hint "received unexpected item" :item item)))

View File

@@ -317,13 +317,7 @@
::climit/enabled (contains? cf/flags :rpc-climit)} ::climit/enabled (contains? cf/flags :rpc-climit)}
:app.rpc/rlimit :app.rpc/rlimit
{::wrk/executor (ig/ref ::wrk/netty-executor) {::wrk/executor (ig/ref ::wrk/netty-executor)}
:app.loggers.mattermost/reporter
(ig/ref :app.loggers.mattermost/reporter)
:app.loggers.database/reporter
(ig/ref :app.loggers.database/reporter)}
:app.rpc/methods :app.rpc/methods
{::http.client/client (ig/ref ::http.client/client) {::http.client/client (ig/ref ::http.client/client)

View File

@@ -90,7 +90,7 @@
[methods] [methods]
(let [methods (update-vals methods peek)] (let [methods (update-vals methods peek)]
(fn [{:keys [params path-params method] :as request}] (fn [{:keys [params path-params method] :as request}]
(let [handler-name (:method-name path-params) (let [handler-name (:type path-params)
etag (yreq/get-header request "if-none-match") etag (yreq/get-header request "if-none-match")
key-id (get request ::http/auth-key-id) key-id (get request ::http/auth-key-id)
@@ -227,8 +227,8 @@
(wrap-authentication cfg $ mdata))) (wrap-authentication cfg $ mdata)))
(defn- process-method (defn- process-method
[cfg wrap-fn [f mdata]] [cfg module wrap-fn [f mdata]]
(l/trc :hint "add method" :module (::module cfg) :type (::type cfg) :name (::sv/name mdata)) (l/trc :hint "add method" :module module :name (::sv/name mdata))
(let [f (wrap-fn cfg f mdata) (let [f (wrap-fn cfg f mdata)
k (keyword (::sv/name mdata))] k (keyword (::sv/name mdata))]
[k [mdata (partial f cfg)]])) [k [mdata (partial f cfg)]]))
@@ -239,7 +239,7 @@
(defn- resolve-methods (defn- resolve-methods
[cfg] [cfg]
(let [cfg (assoc cfg ::module "main" ::type "command" ::metrics-id :rpc-main-timing)] (let [cfg (assoc cfg ::type "command" ::metrics-id :rpc-command-timing)]
(->> (sv/scan-ns (->> (sv/scan-ns
'app.rpc.commands.access-token 'app.rpc.commands.access-token
'app.rpc.commands.audit 'app.rpc.commands.audit
@@ -266,7 +266,7 @@
'app.rpc.commands.verify-token 'app.rpc.commands.verify-token
'app.rpc.commands.viewer 'app.rpc.commands.viewer
'app.rpc.commands.webhooks) 'app.rpc.commands.webhooks)
(map (partial process-method cfg wrap)) (map (partial process-method cfg "rpc" wrap))
(into {})))) (into {}))))
(def ^:private schema:methods-params (def ^:private schema:methods-params
@@ -298,13 +298,13 @@
(defn- resolve-management-methods (defn- resolve-management-methods
[cfg] [cfg]
(let [cfg (assoc cfg ::module "management" ::type "command" ::metrics-id :rpc-management-timing) (let [cfg (assoc cfg ::type "management" ::metrics-id :rpc-management-timing)
mods (cond->> (list 'app.rpc.management.exporter) mods (cond->> (list 'app.rpc.management.exporter)
(contains? cf/flags :nitrate) (contains? cf/flags :nitrate)
(cons 'app.rpc.management.nitrate))] (cons 'app.rpc.management.nitrate))]
(->> (apply sv/scan-ns mods) (->> (apply sv/scan-ns mods)
(map (partial process-method cfg wrap-management)) (map (partial process-method cfg "management" wrap-management))
(into {})))) (into {}))))
(def ^:private schema:management-methods-params (def ^:private schema:management-methods-params
@@ -359,7 +359,7 @@
(let [public-uri (cf/get :public-uri)] (let [public-uri (cf/get :public-uri)]
["/api" ["/api"
["/management" ["/management"
["/methods/:method-name" ["/methods/:type"
{:middleware [[mw/shared-key-auth shared-keys] {:middleware [[mw/shared-key-auth shared-keys]
[session/authz cfg]] [session/authz cfg]]
:handler (make-rpc-handler management-methods)}] :handler (make-rpc-handler management-methods)}]
@@ -370,7 +370,7 @@
:description "MANAGEMENT API")] :description "MANAGEMENT API")]
["/main" ["/main"
["/methods/:method-name" ["/methods/:type"
{:middleware [[mw/cors] {:middleware [[mw/cors]
[sec/client-header-check] [sec/client-header-check]
[session/authz cfg] [session/authz cfg]
@@ -388,7 +388,7 @@
["/openapi" {:handler (redirect (u/join public-uri "/api/main/doc/openapi"))}] ["/openapi" {:handler (redirect (u/join public-uri "/api/main/doc/openapi"))}]
["/openapi.join" {:handler (redirect (u/join public-uri "/api/main/doc/openapi.json"))}] ["/openapi.join" {:handler (redirect (u/join public-uri "/api/main/doc/openapi.json"))}]
["/rpc/command/:method-name" ["/rpc/command/:type"
{:middleware [[mw/cors] {:middleware [[mw/cors]
[sec/client-header-check] [sec/client-header-check]
[session/authz cfg] [session/authz cfg]

View File

@@ -52,8 +52,6 @@
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.config :as cf] [app.config :as cf]
[app.http :as-alias http] [app.http :as-alias http]
[app.loggers.database :as loggers.db]
[app.loggers.mattermost :as loggers.mm]
[app.redis :as rds] [app.redis :as rds]
[app.redis.script :as-alias rscript] [app.redis.script :as-alias rscript]
[app.rpc :as-alias rpc] [app.rpc :as-alias rpc]
@@ -173,9 +171,9 @@
:hint (str/ffmt "looks like '%' does not have a valid format" opts)))) :hint (str/ffmt "looks like '%' does not have a valid format" opts))))
(defmethod process-limit :bucket (defmethod process-limit :bucket
[rconn profile-id now {:keys [::key ::params ::method ::capacity ::interval ::rate] :as limit}] [rconn profile-id now {:keys [::key ::params ::service ::capacity ::interval ::rate] :as limit}]
(let [script (-> bucket-rate-limit-script (let [script (-> bucket-rate-limit-script
(assoc ::rscript/keys [(str key "." method "." profile-id)]) (assoc ::rscript/keys [(str key "." service "." profile-id)])
(assoc ::rscript/vals (conj params (->seconds now)))) (assoc ::rscript/vals (conj params (->seconds now))))
result (rds/eval rconn script) result (rds/eval rconn script)
allowed? (boolean (nth result 0)) allowed? (boolean (nth result 0))
@@ -183,7 +181,7 @@
reset (* (/ (inst-ms interval) rate) reset (* (/ (inst-ms interval) rate)
(- capacity remaining))] (- capacity remaining))]
(l/trace :hint "limit processed" (l/trace :hint "limit processed"
:method method :service service
:limit (name (::name limit)) :limit (name (::name limit))
:strategy (name (::strategy limit)) :strategy (name (::strategy limit))
:opts (::opts limit) :opts (::opts limit)
@@ -195,17 +193,17 @@
(assoc ::lresult/remaining remaining)))) (assoc ::lresult/remaining remaining))))
(defmethod process-limit :window (defmethod process-limit :window
[rconn uid now {:keys [::permits ::unit ::key ::method] :as limit}] [rconn profile-id now {:keys [::permits ::unit ::key ::service] :as limit}]
(let [ts (ct/truncate now unit) (let [ts (ct/truncate now unit)
ttl (ct/diff now (ct/plus ts {unit 1})) ttl (ct/diff now (ct/plus ts {unit 1}))
script (-> window-rate-limit-script script (-> window-rate-limit-script
(assoc ::rscript/keys [(str key "." method "." uid "." (ct/format-inst ts))]) (assoc ::rscript/keys [(str key "." service "." profile-id "." (ct/format-inst ts))])
(assoc ::rscript/vals [permits (->seconds ttl)])) (assoc ::rscript/vals [permits (->seconds ttl)]))
result (rds/eval rconn script) result (rds/eval rconn script)
allowed? (boolean (nth result 0)) allowed? (boolean (nth result 0))
remaining (nth result 1)] remaining (nth result 1)]
(l/trace :hint "limit processed" (l/trace :hint "limit processed"
:method method :service service
:name (name (::name limit)) :name (name (::name limit))
:strategy (name (::strategy limit)) :strategy (name (::strategy limit))
:opts (::opts limit) :opts (::opts limit)
@@ -213,13 +211,12 @@
:remaining remaining) :remaining remaining)
(-> limit (-> limit
(assoc ::lresult/allowed allowed?) (assoc ::lresult/allowed allowed?)
(assoc ::lresult/timestamp ts)
(assoc ::lresult/remaining remaining) (assoc ::lresult/remaining remaining)
(assoc ::lresult/reset (ct/plus ts {unit 1}))))) (assoc ::lresult/reset (ct/plus ts {unit 1})))))
(defn- process-limits (defn- process-limits
[{:keys [::rds/conn] :as cfg} uid limits now] [rconn profile-id limits now]
(let [results (into [] (map (partial process-limit conn uid now)) limits) (let [results (into [] (map (partial process-limit rconn profile-id now)) limits)
remaining (->> results remaining (->> results
(d/index-by ::name ::lresult/remaining) (d/index-by ::name ::lresult/remaining)
(uri/map->query-string)) (uri/map->query-string))
@@ -230,22 +227,11 @@
rejected (d/seek (complement ::lresult/allowed) results)] rejected (d/seek (complement ::lresult/allowed) results)]
(when rejected (when rejected
(let [event {::id (uuid/next) (l/warn :hint "rejected rate limit"
::uid uid :profile-id (str profile-id)
::method (-> rejected ::method name) :limit-service (-> rejected ::service name)
::name (-> rejected ::name name) :limit-name (-> rejected ::name name)
::strategy (-> rejected ::strategy name) :limit-strategy (-> rejected ::strategy name)))
::results results}]
(l/warn :hint "rejected rate limit"
:method (-> rejected ::method name)
:name (-> rejected ::name name)
:strategy (-> rejected ::strategy name)
:uid (str uid)
:report-id (:id event))
(loggers.mm/emit cfg event)
(loggers.db/emit cfg event)))
{::enabled true {::enabled true
::allowed (not (some? rejected)) ::allowed (not (some? rejected))
@@ -258,7 +244,7 @@
[state skey sname] [state skey sname]
(when-let [limits (or (get-in @state [::limits skey]) (when-let [limits (or (get-in @state [::limits skey])
(get-in @state [::limits :default]))] (get-in @state [::limits :default]))]
(into [] (map #(assoc % ::method sname)) limits))) (into [] (map #(assoc % ::service sname)) limits)))
(defn- get-uid (defn- get-uid
[{:keys [::rpc/profile-id] :as params}] [{:keys [::rpc/profile-id] :as params}]
@@ -268,10 +254,10 @@
uuid/zero))) uuid/zero)))
(defn- process-request' (defn- process-request'
[cfg limits params] [{:keys [::rds/conn] :as cfg} limits params]
(try (try
(let [uid (get-uid params) (let [uid (get-uid params)
result (process-limits cfg uid limits (ct/now))] result (process-limits conn uid limits (ct/now))]
(if (contains? cf/flags :soft-rpc-rlimit) (if (contains? cf/flags :soft-rpc-rlimit)
{::enabled false} {::enabled false}
result)) result))
@@ -289,8 +275,8 @@
(assert (or (nil? rlimit) (valid-rlimit-instance? rlimit)) "expected a valid rlimit instance") (assert (or (nil? rlimit) (valid-rlimit-instance? rlimit)) "expected a valid rlimit instance")
(if rlimit (if rlimit
(let [skey (keyword (::rpc/module cfg) (->> mdata ::sv/spec name)) (let [skey (keyword (::rpc/type cfg) (->> mdata ::sv/spec name))
sname (str (::rpc/module cfg) "." (->> mdata ::sv/spec name)) sname (str (::rpc/type cfg) "." (->> mdata ::sv/spec name))
cfg (-> cfg cfg (-> cfg
(assoc ::skey skey) (assoc ::skey skey)
(assoc ::sname sname))] (assoc ::sname sname))]

View File

@@ -33,7 +33,6 @@
java.util.Optional java.util.Optional
java.util.concurrent.atomic.AtomicLong java.util.concurrent.atomic.AtomicLong
org.reactivestreams.Subscriber org.reactivestreams.Subscriber
software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider
software.amazon.awssdk.core.ResponseBytes software.amazon.awssdk.core.ResponseBytes
software.amazon.awssdk.core.async.AsyncRequestBody software.amazon.awssdk.core.async.AsyncRequestBody
software.amazon.awssdk.core.async.AsyncResponseTransformer software.amazon.awssdk.core.async.AsyncResponseTransformer
@@ -200,8 +199,7 @@
(defn- build-s3-client (defn- build-s3-client
[{:keys [::region ::endpoint ::wrk/netty-io-executor]}] [{:keys [::region ::endpoint ::wrk/netty-io-executor]}]
(let [creds-provider (DefaultCredentialsProvider/create) (let [aconfig (-> (ClientAsyncConfiguration/builder)
aconfig (-> (ClientAsyncConfiguration/builder)
(.build)) (.build))
sconfig (-> (S3Configuration/builder) sconfig (-> (S3Configuration/builder)
@@ -223,7 +221,6 @@
builder (.asyncConfiguration ^S3AsyncClientBuilder builder ^ClientAsyncConfiguration aconfig) builder (.asyncConfiguration ^S3AsyncClientBuilder builder ^ClientAsyncConfiguration aconfig)
builder (.httpClient ^S3AsyncClientBuilder builder ^NettyNioAsyncHttpClient hclient) builder (.httpClient ^S3AsyncClientBuilder builder ^NettyNioAsyncHttpClient hclient)
builder (.region ^S3AsyncClientBuilder builder (lookup-region region)) builder (.region ^S3AsyncClientBuilder builder (lookup-region region))
builder (.credentialsProvider ^S3AsyncClientBuilder builder creds-provider)
builder (cond-> ^S3AsyncClientBuilder builder builder (cond-> ^S3AsyncClientBuilder builder
(some? endpoint) (some? endpoint)
(.endpointOverride (URI. (str endpoint))))] (.endpointOverride (URI. (str endpoint))))]
@@ -240,8 +237,7 @@
(defn- build-s3-presigner (defn- build-s3-presigner
[{:keys [::region ::endpoint]}] [{:keys [::region ::endpoint]}]
(let [creds-provider (DefaultCredentialsProvider/create) (let [config (-> (S3Configuration/builder)
config (-> (S3Configuration/builder)
(cond-> (some? endpoint) (.pathStyleAccessEnabled true)) (cond-> (some? endpoint) (.pathStyleAccessEnabled true))
(.build))] (.build))]
@@ -249,7 +245,6 @@
(cond-> (some? endpoint) (.endpointOverride (URI. (str endpoint)))) (cond-> (some? endpoint) (.endpointOverride (URI. (str endpoint))))
(.region (lookup-region region)) (.region (lookup-region region))
(.serviceConfiguration ^S3Configuration config) (.serviceConfiguration ^S3Configuration config)
(.credentialsProvider creds-provider)
(.build)))) (.build))))
(defn- write-input-stream (defn- write-input-stream

View File

@@ -104,13 +104,13 @@
(assoc-in [::db/pool ::db/password] (:database-password config)) (assoc-in [::db/pool ::db/password] (:database-password config))
(assoc-in [:app.rpc/methods :app.setup/templates] templates) (assoc-in [:app.rpc/methods :app.setup/templates] templates)
(assoc-in [:app.rpc/methods :app.setup/templates] templates) (assoc-in [:app.rpc/methods :app.setup/templates] templates)
(update :app.rpc/rlimit assoc (update :app.rpc/methods
:app.loggers.mattermost/reporter nil (fn [state]
:app.loggers.database/reporter nil) (-> state
(update :app.rpc/methods assoc (assoc :app.setup/templates templates)
:app.setup/templates templates (assoc :app.loggers.mattermost/reporter nil)
:app.loggers.mattermost/reporter nil (assoc :app.loggers.database/reporter nil))))
:app.loggers.database/reporter nil)
(dissoc :app.srepl/server (dissoc :app.srepl/server
:app.http/server :app.http/server
:app.http/route :app.http/route

View File

@@ -35,7 +35,7 @@
[::sm/text {:error/fn token-value-empty-fn}]) [::sm/text {:error/fn token-value-empty-fn}])
(def schema:token-value-font-family (def schema:token-value-font-family
[:vector ::sm/text]) [:vector :string])
(def schema:token-value-typography-map (def schema:token-value-typography-map
[:map [:map

View File

@@ -43,13 +43,9 @@
(> dy dx) (> dy dx)
(assoc :x (- (:x point) (* sx (- dy dx))))))) (assoc :x (- (:x point) (* sx (- dy dx)))))))
(defn resize-shape [{:keys [x y width height] :as shape} initial point lock? mod? snap-pixel?] (defn resize-shape [{:keys [x y width height] :as shape} initial point lock? mod?]
(if (and (some? x) (some? y) (some? width) (some? height)) (if (and (some? x) (some? y) (some? width) (some? height))
(let [draw-rect (cond-> (grc/make-rect initial (cond-> point lock? (adjust-ratio initial))) (let [draw-rect (grc/make-rect initial (cond-> point lock? (adjust-ratio initial)))
snap-pixel?
(-> (update :width max 1)
(update :height max 1)))
shape-rect (grc/make-rect x y width height) shape-rect (grc/make-rect x y width height)
scalev (gpt/point (/ (:width draw-rect) scalev (gpt/point (/ (:width draw-rect)
@@ -68,8 +64,8 @@
(ctm/move movev))))) (ctm/move movev)))))
shape)) shape))
(defn- update-drawing [state initial point lock? mod? snap-pixel?] (defn update-drawing [state initial point lock? mod?]
(update-in state [:workspace-drawing :object] resize-shape initial point lock? mod? snap-pixel?)) (update-in state [:workspace-drawing :object] resize-shape initial point lock? mod?))
(defn move-drawing (defn move-drawing
[{:keys [x y]}] [{:keys [x y]}]
@@ -124,7 +120,7 @@
(rx/map move-drawing)) (rx/map move-drawing))
(->> ms/mouse-position (->> ms/mouse-position
(rx/filter #(> (* (gpt/distance % initial) zoom) 10)) (rx/filter #(> (gpt/distance % initial) (/ 2 zoom)))
;; Take until before the snap calculation otherwise we could cancel the snap in the worker ;; Take until before the snap calculation otherwise we could cancel the snap in the worker
;; and its a problem for fast moving drawing ;; and its a problem for fast moving drawing
(rx/take-until stopper) (rx/take-until stopper)
@@ -135,7 +131,7 @@
(rx/map (partial array/conj current))))) (rx/map (partial array/conj current)))))
(rx/map (rx/map
(fn [[_ shift? mod? point]] (fn [[_ shift? mod? point]]
#(update-drawing % initial (cond-> point snap-pixel? (gpt/round-step 1)) shift? mod? snap-pixel?)))))) #(update-drawing % initial (cond-> point snap-pixel? (gpt/round-step 1)) shift? mod?))))))
(->> (rx/of (common/handle-finish-drawing)) (->> (rx/of (common/handle-finish-drawing))
(rx/delay 100))))))) (rx/delay 100)))))))

View File

@@ -126,6 +126,6 @@
(defn check-permission (defn check-permission
[plugin-id permission] [plugin-id permission]
(or (= plugin-id "00000000-0000-0000-0000-000000000000") (or (= plugin-id "TEST")
(let [{:keys [permissions]} (dm/get-in @registry [:data plugin-id])] (let [{:keys [permissions]} (dm/get-in @registry [:data plugin-id])]
(contains? permissions permission)))) (contains? permissions permission))))

View File

@@ -11,7 +11,6 @@
[app.common.files.helpers :as cfh] [app.common.files.helpers :as cfh]
[app.common.geom.rect :as grc] [app.common.geom.rect :as grc]
[app.common.geom.shapes :as gsh] [app.common.geom.shapes :as gsh]
[app.common.json :as json]
[app.common.path-names :as cpn] [app.common.path-names :as cpn]
[app.common.record :as crc] [app.common.record :as crc]
[app.common.schema :as sm] [app.common.schema :as sm]
@@ -1296,7 +1295,7 @@
(get :applied-tokens))] (get :applied-tokens))]
(reduce (reduce
(fn [acc [prop name]] (fn [acc [prop name]]
(obj/set! acc (json/write-camel-key prop) name)) (obj/set! acc (d/name prop) name))
#js {} #js {}
tokens)))} tokens)))}

View File

@@ -16,7 +16,7 @@
[app.main.data.workspace.tokens.application :as dwta] [app.main.data.workspace.tokens.application :as dwta]
[app.main.data.workspace.tokens.library-edit :as dwtl] [app.main.data.workspace.tokens.library-edit :as dwtl]
[app.main.store :as st] [app.main.store :as st]
;; [app.plugins.shape :as shape] [app.plugins.shape :as shape]
[app.plugins.utils :as u] [app.plugins.utils :as u]
[app.util.object :as obj] [app.util.object :as obj]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
@@ -113,17 +113,13 @@
:applyToShapes :applyToShapes
{:schema [:tuple {:schema [:tuple
;; FIXME: the schema decoder is interpreting the array of shape-proxys and converting [:vector [:fn shape/shape-proxy?]]
;; them to plain maps. For now we adapt the schema to accept it, but the decoder [:maybe [:set ::sm/keyword]]]
;; should be fixed to keep the original proxy objects coming from the plugin.
;; [:vector [:fn shape/shape-proxy?]]
[:vector [:map [:id ::sm/uuid]]]
[:maybe [:set [:set [:and ::sm/keyword [:fn cto/token-attr?]]]]]]
:fn (fn [shapes attrs] :fn (fn [shapes attrs]
(apply-token-to-shapes file-id set-id id (map :id shapes) attrs))} (apply-token-to-shapes file-id set-id id (map :id shapes) attrs))}
:applyToSelected :applyToSelected
{:schema [:tuple [:maybe [:set [:and ::sm/keyword [:fn cto/token-attr?]]]]] {:schema [:tuple [:maybe [:set ::sm/keyword]]]
:fn (fn [attrs] :fn (fn [attrs]
(let [selected (get-in @st/state [:workspace-local :selected])] (let [selected (get-in @st/state [:workspace-local :selected])]
(apply-token-to-shapes file-id set-id id selected attrs)))})) (apply-token-to-shapes file-id set-id id selected attrs)))}))

View File

@@ -10,6 +10,7 @@
(:refer-clojure :exclude [set! new get merge clone contains? array? into-array reify class]) (:refer-clojure :exclude [set! new get merge clone contains? array? into-array reify class])
#?(:cljs (:require-macros [app.util.object])) #?(:cljs (:require-macros [app.util.object]))
(:require (:require
[app.common.data :as d]
[app.common.json :as json] [app.common.json :as json]
[app.common.schema :as sm] [app.common.schema :as sm]
[clojure.core :as c] [clojure.core :as c]
@@ -156,6 +157,7 @@
this-sym (with-meta (gensym (str rsym "-this-")) {:tag 'js}) this-sym (with-meta (gensym (str rsym "-this-")) {:tag 'js})
target-sym (with-meta (gensym (str rsym "-target-")) {:tag 'js}) target-sym (with-meta (gensym (str rsym "-target-")) {:tag 'js})
cause-sym (gensym "cause-")
make-sym make-sym
(fn [pname prefix] (fn [pname prefix]
@@ -176,6 +178,7 @@
wrap (c/get params :wrap) wrap (c/get params :wrap)
schema-1 (c/get params :schema-1) schema-1 (c/get params :schema-1)
this? (c/get params :this false) this? (c/get params :this false)
on-error (c/get params :on-error)
decode-expr decode-expr
(c/get params :decode/fn) (c/get params :decode/fn)
@@ -214,7 +217,16 @@
(with-meta {:tag 'function})) (with-meta {:tag 'function}))
val-sym val-sym
(gensym (str "val-" (str/slug pname) "-"))] (gensym (str "val-" (str/slug pname) "-"))
wrap-error-handling
(if on-error
(fn [expr]
`(try
~expr
(catch :default ~cause-sym
(~on-error ~cause-sym))))
identity)]
(concat (concat
(when wrap (when wrap
@@ -226,8 +238,13 @@
`(fn [] `(fn []
(let [~this-sym (~'js* "this") (let [~this-sym (~'js* "this")
~fn-sym ~get-expr] ~fn-sym ~get-expr]
(.call ~fn-sym ~this-sym ~this-sym))) ~(wrap-error-handling
get-expr)]) `(.call ~fn-sym ~this-sym ~this-sym))))
`(fn []
(let [~this-sym (~'js* "this")
~fn-sym ~get-expr]
~(wrap-error-handling
`(.call ~fn-sym ~this-sym)))))])
(when set-expr (when set-expr
[schema-sym schema-n [schema-sym schema-n
@@ -241,28 +258,35 @@
(make-sym pname "set-fn") (make-sym pname "set-fn")
`(fn [~val-sym] `(fn [~val-sym]
(let [~this-sym (~'js* "this") ~(wrap-error-handling
~fn-sym ~set-expr `(let [~this-sym (~'js* "this")
~fn-sym ~set-expr
;; We only emit schema and coercer bindings if ;; We only emit schema and coercer bindings if
;; schema-n is provided ;; schema-n is provided
~@(if (some? schema-n) ~@(if (some? schema-n)
[schema-sym `(if (fn? ~schema-sym) [schema-sym
(~schema-sym ~val-sym) `(if (fn? ~schema-sym)
~schema-sym) (~schema-sym ~val-sym)
~schema-sym)
coercer-sym `(if (nil? ~coercer-sym) coercer-sym
(sm/coercer ~schema-sym) `(if (nil? ~coercer-sym)
~coercer-sym) (sm/coercer ~schema-sym)
val-sym (if (not= decode-expr 'app.common.json/->clj) ~coercer-sym)
`(~decode-sym ~val-sym)
`(~decode-sym ~val-sym ~decode-options))
val-sym `(~coercer-sym ~val-sym)]
[])]
~(if this? val-sym
`(.call ~fn-sym ~this-sym ~this-sym ~val-sym) (if (not= decode-expr 'app.common.json/->clj)
`(.call ~fn-sym ~this-sym ~val-sym))))]) `(~decode-sym ~val-sym)
`(~decode-sym ~val-sym ~decode-options))
val-sym
`(~coercer-sym ~val-sym)]
[])]
~(if this?
`(.call ~fn-sym ~this-sym ~this-sym ~val-sym)
`(.call ~fn-sym ~this-sym ~val-sym)))))])
(when fn-expr (when fn-expr
[schema-sym (or schema-n schema-1) [schema-sym (or schema-n schema-1)
@@ -275,7 +299,12 @@
(make-sym pname "get-fn") (make-sym pname "get-fn")
`(fn [] `(fn []
(let [~this-sym (~'js* "this") (let [~this-sym (~'js* "this")
~fn-sym ~fn-expr ~fn-sym ~(if (and (list? fn-expr)
(= 'fn (first fn-expr)))
(let [[sa sb & sother] fn-expr]
`(~sa ~sb ~(wrap-error-handling `(do ~@sother))))
fn-expr)
~fn-sym ~(if this? ~fn-sym ~(if this?
`(.bind ~fn-sym ~this-sym ~this-sym) `(.bind ~fn-sym ~this-sym ~this-sym)
`(.bind ~fn-sym ~this-sym)) `(.bind ~fn-sym ~this-sym))
@@ -284,25 +313,31 @@
;; schema-n or schema-1 is provided ;; schema-n or schema-1 is provided
~@(if (or schema-n schema-1) ~@(if (or schema-n schema-1)
[fn-sym `(fn* [~@(if schema-1 [val-sym] [])] [fn-sym `(fn* [~@(if schema-1 [val-sym] [])]
(let [~@(if schema-n ~(wrap-error-handling
[val-sym `(into-array (cljs.core/js-arguments))] `(let [~@(if schema-n
[]) [val-sym `(into-array (cljs.core/js-arguments))]
~val-sym ~(if (not= decode-expr 'app.common.json/->clj) [])
`(~decode-sym ~val-sym) ~val-sym
`(~decode-sym ~val-sym ~decode-options)) ~(if (not= decode-expr 'app.common.json/->clj)
`(~decode-sym ~val-sym)
`(~decode-sym ~val-sym ~decode-options))
~schema-sym (if (fn? ~schema-sym) ~schema-sym
(~schema-sym ~val-sym) (if (fn? ~schema-sym)
~schema-sym) (~schema-sym ~val-sym)
~schema-sym)
~coercer-sym (if (nil? ~coercer-sym) ~coercer-sym
(sm/coercer ~schema-sym) (if (nil? ~coercer-sym)
~coercer-sym) (sm/coercer ~schema-sym)
~coercer-sym)
~val-sym (~coercer-sym ~val-sym)] ~val-sym
~(if schema-1 (~coercer-sym ~val-sym)]
`(~fn-sym ~val-sym)
`(apply ~fn-sym ~val-sym))))] ~(if schema-1
`(~fn-sym ~val-sym)
`(apply ~fn-sym ~val-sym)))))]
[])] [])]
~(if wrap ~(if wrap
`(~wrap-sym ~fn-sym) `(~wrap-sym ~fn-sym)
@@ -373,14 +408,19 @@
(= :property curr) (= :property curr)
(let [definition (first params)] (let [definition (first params)]
(prn definition (meta definition))
(if (some? definition) (if (some? definition)
(let [definition (if (map? definition) (let [definition (if (map? definition)
(c/merge {:wrap (:wrap tmeta)} definition) (c/merge {:wrap (:wrap tmeta)
:on-error (:on-error tmeta)}
definition)
(-> {:enumerable false} (-> {:enumerable false}
(c/merge (meta definition)) (c/merge (meta definition))
(assoc :wrap (:wrap tmeta)) (assoc :wrap (:wrap tmeta))
(assoc :on-error (:on-error tmeta))
(assoc :fn definition) (assoc :fn definition)
(dissoc :get :set))) (dissoc :get :set :line :column)
(d/without-nils)))
definition (assoc definition :name (name ckey))] definition (assoc definition :name (name ckey))]
(recur (rest params) (recur (rest params)
@@ -425,6 +465,13 @@
(let [o (get o type-symbol)] (let [o (get o type-symbol)]
(= o t)))) (= o t))))
#?(:cljs
(def Proxy
(app.util.object/class
:name "Proxy"
:extends js/Object
:constructor (constantly nil))))
(defmacro reify (defmacro reify
"A domain specific variation of reify that creates anonymous objects "A domain specific variation of reify that creates anonymous objects
on demand with the ability to assign protocol implementations and on demand with the ability to assign protocol implementations and
@@ -442,7 +489,7 @@
obj-sym obj-sym
(gensym "obj-")] (gensym "obj-")]
`(let [~obj-sym (cljs.core/js-obj) `(let [~obj-sym (new Proxy)
~f-sym (fn [] ~type-name)] ~f-sym (fn [] ~type-name)]
(add-properties! ~obj-sym (add-properties! ~obj-sym
{:name ~'js/Symbol.toStringTag {:name ~'js/Symbol.toStringTag

View File

@@ -18,7 +18,7 @@
(let [;; ==== Setup (let [;; ==== Setup
store (ths/setup-store (cthf/sample-file :file1 :page-label :page1)) store (ths/setup-store (cthf/sample-file :file1 :page-label :page1))
^js context (api/create-context "00000000-0000-0000-0000-000000000000") ^js context (api/create-context "TEST")
_ (set! st/state store) _ (set! st/state store)

View File

@@ -3,6 +3,21 @@ compatibility_date = "2025-01-01"
assets = { directory = "../../dist/apps/colors-to-tokens-plugin/browser" } assets = { directory = "../../dist/apps/colors-to-tokens-plugin/browser" }
[observability]
enabled = true
head_sampling_rate = 1
[observability.logs]
enabled = true
head_sampling_rate = 1
persist = true
invocation_logs = true
[observability.traces]
enabled = false
persist = true
head_sampling_rate = 1
[[routes]] [[routes]]
pattern = "WORKER_URI" pattern = "WORKER_URI"
custom_domain = true custom_domain = true

View File

@@ -3,6 +3,21 @@ compatibility_date = "2025-01-01"
assets = { directory = "../../dist/apps/contrast-plugin/browser" } assets = { directory = "../../dist/apps/contrast-plugin/browser" }
[observability]
enabled = true
head_sampling_rate = 1
[observability.logs]
enabled = true
head_sampling_rate = 1
persist = true
invocation_logs = true
[observability.traces]
enabled = false
persist = true
head_sampling_rate = 1
[[routes]] [[routes]]
pattern = "WORKER_URI" pattern = "WORKER_URI"
custom_domain = true custom_domain = true

View File

@@ -3,6 +3,21 @@ compatibility_date = "2025-01-01"
assets = { directory = "../../dist/apps/create-palette-plugin" } assets = { directory = "../../dist/apps/create-palette-plugin" }
[observability]
enabled = true
head_sampling_rate = 1
[observability.logs]
enabled = true
head_sampling_rate = 1
persist = true
invocation_logs = true
[observability.traces]
enabled = false
persist = true
head_sampling_rate = 1
[[routes]] [[routes]]
pattern = "WORKER_URI" pattern = "WORKER_URI"
custom_domain = true custom_domain = true

View File

@@ -28,5 +28,5 @@ export default [
files: ['**/*.js', '**/*.jsx'], files: ['**/*.js', '**/*.jsx'],
rules: {}, rules: {},
}, },
{ ignores: ['vite.config.ts', 'vitest.setup.ts'] }, { ignores: ['vite.config.ts'] },
]; ];

View File

File diff suppressed because it is too large Load Diff

View File

@@ -29,7 +29,6 @@ describe('Plugins', () => {
it('create grid layout', async () => { it('create grid layout', async () => {
const agent = await Agent(); const agent = await Agent();
const result = await agent.runCode(grid.toString(), { const result = await agent.runCode(grid.toString(), {
screenshot: 'create-gridlayout', screenshot: 'create-gridlayout',
}); });
@@ -84,9 +83,9 @@ describe('Plugins', () => {
it('comments', async () => { it('comments', async () => {
const agent = await Agent(); const agent = await Agent();
console.log(comments.toString());
const result = await agent.runCode(comments.toString(), { const result = await agent.runCode(comments.toString(), {
screenshot: 'create-comments', screenshot: 'create-comments',
avoidSavedStatus: true,
}); });
expect(result).toMatchSnapshot(); expect(result).toMatchSnapshot();
}); });

View File

@@ -1,4 +1,4 @@
import puppeteer, { ConsoleMessage } from 'puppeteer'; import puppeteer from 'puppeteer';
import { PenpotApi } from './api'; import { PenpotApi } from './api';
import { getFileUrl } from './get-file-url'; import { getFileUrl } from './get-file-url';
import { idObjectToArray } from './clean-id'; import { idObjectToArray } from './clean-id';
@@ -56,16 +56,10 @@ export async function Agent() {
console.log('File URL:', fileUrl); console.log('File URL:', fileUrl);
console.log('Launching browser...'); console.log('Launching browser...');
const browser = await puppeteer.launch({ const browser = await puppeteer.launch({});
headless: process.env['E2E_HEADLESS'] !== 'false',
args: ['--ignore-certificate-errors'],
});
const page = await browser.newPage(); const page = await browser.newPage();
await page.setViewport({ width: 1920, height: 1080 }); await page.setViewport({ width: 1920, height: 1080 });
await page.setExtraHTTPHeaders({
'X-Client': 'plugins/e2e:puppeter',
});
console.log('Setting authentication cookie...'); console.log('Setting authentication cookie...');
page.setCookie({ page.setCookie({
@@ -91,11 +85,8 @@ export async function Agent() {
const finish = async () => { const finish = async () => {
console.log('Deleting file and closing browser...'); console.log('Deleting file and closing browser...');
// TODO await penpotApi.deleteFile(file['~:id']);
// await penpotApi.deleteFile(file['~:id']); await browser.close();
if (process.env['E2E_CLOSE_BROWSER'] !== 'false') {
await browser.close();
}
console.log('Clean up done.'); console.log('Clean up done.');
}; };
@@ -105,9 +96,11 @@ export async function Agent() {
options: { options: {
screenshot?: string; screenshot?: string;
autoFinish?: boolean; autoFinish?: boolean;
avoidSavedStatus?: boolean;
} = { } = {
screenshot: '', screenshot: '',
autoFinish: true, autoFinish: true,
avoidSavedStatus: false,
}, },
) { ) {
const autoFinish = options.autoFinish ?? true; const autoFinish = options.autoFinish ?? true;
@@ -116,27 +109,28 @@ export async function Agent() {
await page.evaluate((testingPlugin) => { await page.evaluate((testingPlugin) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
(globalThis as any).ɵloadPlugin({ (globalThis as any).ɵloadPlugin({
pluginId: '00000000-0000-0000-0000-000000000000', pluginId: 'TEST',
name: 'Test', name: 'Test',
code: ` code: `
(${testingPlugin})(); (${testingPlugin})();
`, `,
icon: '', icon: '',
description: '', description: '',
permissions: [ permissions: ['content:read', 'content:write'],
'content:read',
'content:write',
'library:read',
'library:write',
'user:read',
'comment:read',
'comment:write',
'allow:downloads',
'allow:localstorage',
],
}); });
}, code); }, code);
if (!options.avoidSavedStatus) {
console.log('Waiting for save status...');
await page.waitForSelector(
'.main_ui_workspace_right_header__saved-status',
{
timeout: 10000,
},
);
console.log('Save status found.');
}
if (options.screenshot && screenshotsEnable) { if (options.screenshot && screenshotsEnable) {
console.log('Taking screenshot:', options.screenshot); console.log('Taking screenshot:', options.screenshot);
await page.screenshot({ await page.screenshot({
@@ -144,55 +138,30 @@ export async function Agent() {
}); });
} }
const result = await new Promise((resolve) => { return new Promise((resolve) => {
const handleConsole = async (msg: ConsoleMessage) => { page.once('console', async (msg) => {
const args = (await Promise.all( const args = (await Promise.all(
msg.args().map((arg) => arg.jsonValue()), msg.args().map((arg) => arg.jsonValue()),
)) as unknown[]; )) as Record<string, unknown>[];
const type = args[0]; const result = Object.values(args[1]) as Shape[];
const data = args[1];
if (type !== 'objects' || !data || typeof data !== 'object') {
console.log('Invalid console message, waiting for valid one...');
page.once('console', handleConsole);
return;
}
const result = Object.values(data) as Shape[];
replaceIds(result); replaceIds(result);
console.log('IDs replaced in result.'); console.log('IDs replaced in result.');
resolve(result); resolve(result);
};
page.once('console', handleConsole); if (autoFinish) {
console.log('Auto finish enabled. Cleaning up...');
finish();
}
});
console.log('Evaluating debug.dump_objects...'); console.log('Evaluating debug.dump_objects...');
page.evaluate(` page.evaluate(`
debug.dump_objects(); debug.dump_objects();
`); `);
}); });
await page.waitForNetworkIdle({ idleTime: 2000 });
// Wait for the update-file API call to complete
if (process.env['E2E_WAIT_API_RESPONSE'] === 'true') {
await page.waitForResponse(
(response) =>
response.url().includes('api/main/methods/update-file') &&
response.status() === 200,
{ timeout: 10000 },
);
}
if (autoFinish) {
console.log('Auto finish enabled. Cleaning up...');
await finish();
}
return result;
}, },
finish, finish,
}; };

View File

@@ -1,34 +1,31 @@
import { FileRpc } from '../models/file-rpc.model'; import { FileRpc } from '../models/file-rpc.model';
const apiUrl = 'https://localhost:3449';
const apiUrl = 'http://localhost:3449';
export async function PenpotApi() { export async function PenpotApi() {
if (!process.env['E2E_LOGIN_EMAIL']) { if (!process.env['E2E_LOGIN_EMAIL']) {
throw new Error('E2E_LOGIN_EMAIL not set'); throw new Error('E2E_LOGIN_EMAIL not set');
} }
const body = JSON.stringify({
email: process.env['E2E_LOGIN_EMAIL'],
password: process.env['E2E_LOGIN_PASSWORD'],
});
const resultLoginRequest = await fetch( const resultLoginRequest = await fetch(
`${apiUrl}/api/main/methods/login-with-password`, `${apiUrl}/api/rpc/command/login-with-password`,
{ {
credentials: 'include',
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/transit+json',
}, },
body: body, body: JSON.stringify({
'~:email': process.env['E2E_LOGIN_EMAIL'],
'~:password': process.env['E2E_LOGIN_PASSWORD'],
}),
}, },
); );
const loginData = await resultLoginRequest.json(); const loginData = await resultLoginRequest.json();
const authToken = resultLoginRequest.headers const authToken = resultLoginRequest.headers
.getSetCookie() .get('set-cookie')
.find((cookie: string) => cookie.startsWith('auth-token=')) ?.split(';')
?.split(';')[0]; .at(0);
if (!authToken) { if (!authToken) {
throw new Error('Login failed'); throw new Error('Login failed');
@@ -38,7 +35,7 @@ export async function PenpotApi() {
getAuth: () => authToken, getAuth: () => authToken,
createFile: async () => { createFile: async () => {
const createFileRequest = await fetch( const createFileRequest = await fetch(
`${apiUrl}/api/main/methods/create-file`, `${apiUrl}/api/rpc/command/create-file`,
{ {
method: 'POST', method: 'POST',
headers: { headers: {
@@ -54,9 +51,6 @@ export async function PenpotApi() {
'fdata/objects-map', 'fdata/objects-map',
'fdata/pointer-map', 'fdata/pointer-map',
'fdata/shape-data-type', 'fdata/shape-data-type',
'fdata/path-data',
'design-tokens/v1',
'variants/v1',
'components/v2', 'components/v2',
'styles/v2', 'styles/v2',
'layout/grid', 'layout/grid',
@@ -67,13 +61,11 @@ export async function PenpotApi() {
}, },
); );
const fileData = (await createFileRequest.json()) as FileRpc; return (await createFileRequest.json()) as FileRpc;
console.log('File data received:', fileData);
return fileData;
}, },
deleteFile: async (fileId: string) => { deleteFile: async (fileId: string) => {
const deleteFileRequest = await fetch( const deleteFileRequest = await fetch(
`${apiUrl}/api/main/methods/delete-file`, `${apiUrl}/api/rpc/command/delete-file`,
{ {
method: 'POST', method: 'POST',
headers: { headers: {

View File

@@ -6,5 +6,5 @@ export function getFileUrl(file: FileRpc) {
const fileId = cleanId(file['~:id']); const fileId = cleanId(file['~:id']);
const pageId = cleanId(file['~:data']['~:pages'][0]); const pageId = cleanId(file['~:data']['~:pages'][0]);
return `https://localhost:3449/#/workspace/${projectId}/${fileId}?page-id=${pageId}`; return `http://localhost:3449/#/workspace/${projectId}/${fileId}?page-id=${pageId}`;
} }

View File

@@ -7,27 +7,13 @@ export default defineConfig({
testTimeout: 20000, testTimeout: 20000,
watch: false, watch: false,
globals: true, globals: true,
environment: 'node', environment: 'happy-dom',
environmentOptions: {
happyDOM: {
settings: {
disableCSSFileLoading: true,
disableJavaScriptFileLoading: true,
disableJavaScriptEvaluation: true,
enableFileSystemHttpRequests: false,
navigator: {
userAgent:
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
},
},
},
},
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'], reporters: ['default'],
coverage: { coverage: {
reportsDirectory: '../coverage/e2e', reportsDirectory: '../coverage/e2e',
provider: 'v8', provider: 'v8',
}, },
setupFiles: ['dotenv/config', 'vitest.setup.ts'], setupFiles: ['dotenv/config'],
}, },
}); });

View File

@@ -1 +0,0 @@
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';

View File

@@ -3,6 +3,21 @@ compatibility_date = "2025-01-01"
assets = { directory = "../../dist/apps/icons-plugin/browser" } assets = { directory = "../../dist/apps/icons-plugin/browser" }
[observability]
enabled = true
head_sampling_rate = 1
[observability.logs]
enabled = true
head_sampling_rate = 1
persist = true
invocation_logs = true
[observability.traces]
enabled = false
persist = true
head_sampling_rate = 1
[[routes]] [[routes]]
pattern = "WORKER_URI" pattern = "WORKER_URI"
custom_domain = true custom_domain = true

View File

@@ -3,6 +3,21 @@ compatibility_date = "2025-01-01"
assets = { directory = "../../dist/apps/lorem-ipsum-plugin/browser" } assets = { directory = "../../dist/apps/lorem-ipsum-plugin/browser" }
[observability]
enabled = true
head_sampling_rate = 1
[observability.logs]
enabled = true
head_sampling_rate = 1
persist = true
invocation_logs = true
[observability.traces]
enabled = false
persist = true
head_sampling_rate = 1
[[routes]] [[routes]]
pattern = "WORKER_URI" pattern = "WORKER_URI"
custom_domain = true custom_domain = true

View File

@@ -251,14 +251,7 @@ function applyToken(
token.applyToSelected(properties); token.applyToSelected(properties);
} }
// Alternative way // Alternatve way
//
// const selection = penpot.selection;
// if (token && selection) {
// token.applyToShapes(selection, properties);
// }
// Other alternative way
// //
// const selection = penpot.selection; // const selection = penpot.selection;
// if (token && selection) { // if (token && selection) {

View File

@@ -3,6 +3,21 @@ compatibility_date = "2025-01-01"
assets = { directory = "../../dist/apps/rename-layers-plugin/browser" } assets = { directory = "../../dist/apps/rename-layers-plugin/browser" }
[observability]
enabled = true
head_sampling_rate = 1
[observability.logs]
enabled = true
head_sampling_rate = 1
persist = true
invocation_logs = true
[observability.traces]
enabled = false
persist = true
head_sampling_rate = 1
[[routes]] [[routes]]
pattern = "WORKER_URI" pattern = "WORKER_URI"
custom_domain = true custom_domain = true

View File

@@ -3,6 +3,21 @@ compatibility_date = "2025-01-01"
assets = { directory = "../../dist/apps/table-plugin/browser" } assets = { directory = "../../dist/apps/table-plugin/browser" }
[observability]
enabled = true
head_sampling_rate = 1
[observability.logs]
enabled = true
head_sampling_rate = 1
persist = true
invocation_logs = true
[observability.traces]
enabled = false
persist = true
head_sampling_rate = 1
[[routes]] [[routes]]
pattern = "WORKER_URI" pattern = "WORKER_URI"
custom_domain = true custom_domain = true

View File

@@ -4,15 +4,12 @@
1. **Configure Environment Variables** 1. **Configure Environment Variables**
Create and populate the `apps/e2e/.env` file with a valid user mail & password: Create and populate the `.env` file with a valid user mail & password:
```env ```env
E2E_LOGIN_EMAIL="test@penpot.app" E2E_LOGIN_EMAIL="test@penpot.app"
E2E_LOGIN_PASSWORD="123123123" E2E_LOGIN_PASSWORD="123123123"
E2E_SCREENSHOTS="true" # Enable/disable screenshots (default: false) E2E_SCREENSHOTS= "true"
E2E_HEADLESS="false" # Run browser in headless mode (default: true)
E2E_CLOSE_BROWSER="true" # Close browser after tests (default: true)
E2E_WAIT_API_RESPONSE="false" # Wait for update-file API response (default: false)
``` ```
2. **Run E2E Tests** 2. **Run E2E Tests**
@@ -27,7 +24,7 @@
1. **Adding Tests** 1. **Adding Tests**
Place your test files in the `apps/e2e/src/**/*.spec.ts` directory. Below is an example of a test file: Place your test files in the `/apps/e2e/src/**/*.spec.ts` directory. Below is an example of a test file:
```ts ```ts
import testingPlugin from './plugins/create-board-text-rect'; import testingPlugin from './plugins/create-board-text-rect';
@@ -80,5 +77,5 @@
If you need to refresh all the snapshopts run the test with the update option: If you need to refresh all the snapshopts run the test with the update option:
```bash ```bash
pnpm run test:e2e --update pnpm run test:e2e -- --update
``` ```

View File

@@ -3744,7 +3744,7 @@ export interface ShapeBase extends PluginData {
* and the value set to the attributes will depend on which sets are active * and the value set to the attributes will depend on which sets are active
* (and will change if different sets or themes are activated later). * (and will change if different sets or themes are activated later).
*/ */
readonly tokens: { [property in TokenProperty]: string }; readonly tokens: { [property: string]: string };
/** /**
* @return Returns true if the current shape is inside a component instance * @return Returns true if the current shape is inside a component instance
@@ -5221,7 +5221,7 @@ type TokenDimensionProps =
| 'y' | 'y'
// Stroke width // Stroke width
| 'strokeWidth'; | 'stroke-width';
/** /**
* The properties that a FontFamilies token can be applied to. * The properties that a FontFamilies token can be applied to.

View File

@@ -28,9 +28,7 @@ export const initPluginsRuntime = (contextBuilder: (id: string) => Context) => {
try { try {
console.log('%c[PLUGINS] Initialize runtime', 'color: #008d7c'); console.log('%c[PLUGINS] Initialize runtime', 'color: #008d7c');
setContextBuilder(contextBuilder); setContextBuilder(contextBuilder);
globalThisAny$.ɵcontext = contextBuilder( globalThisAny$.ɵcontext = contextBuilder('TEST');
'00000000-0000-0000-0000-000000000000',
);
globalThis.ɵloadPlugin = ɵloadPlugin; globalThis.ɵloadPlugin = ɵloadPlugin;
globalThis.ɵloadPluginByUrl = ɵloadPluginByUrl; globalThis.ɵloadPluginByUrl = ɵloadPluginByUrl;
globalThis.ɵunloadPlugin = ɵunloadPlugin; globalThis.ɵunloadPlugin = ɵunloadPlugin;

View File

@@ -3,6 +3,21 @@ compatibility_date = "2025-01-01"
assets = { directory = "dist/doc" } assets = { directory = "dist/doc" }
[observability]
enabled = true
head_sampling_rate = 1
[observability.logs]
enabled = true
head_sampling_rate = 1
persist = true
invocation_logs = true
[observability.traces]
enabled = false
persist = true
head_sampling_rate = 1
[[routes]] [[routes]]
pattern = "WORKER_URI" pattern = "WORKER_URI"
custom_domain = true custom_domain = true

View File

@@ -3,6 +3,21 @@ compatibility_date = "2025-01-01"
assets = { directory = "dist/apps/example-styles" } assets = { directory = "dist/apps/example-styles" }
[observability]
enabled = true
head_sampling_rate = 1
[observability.logs]
enabled = true
head_sampling_rate = 1
persist = true
invocation_logs = true
[observability.traces]
enabled = false
persist = true
head_sampling_rate = 1
[[routes]] [[routes]]
pattern = "WORKER_URI" pattern = "WORKER_URI"
custom_domain = true custom_domain = true