parse-stack-next 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.bundle/config +2 -0
- data/.env.sample +112 -0
- data/.env.test +10 -0
- data/.github/workflows/ruby.yml +36 -0
- data/.gitignore +49 -0
- data/.ruby-version +1 -0
- data/.solargraph.yml +22 -0
- data/CHANGELOG.md +5816 -0
- data/Gemfile +30 -0
- data/Gemfile.lock +175 -0
- data/LICENSE.txt +23 -0
- data/Makefile +63 -0
- data/README.md +5655 -0
- data/Rakefile +573 -0
- data/bin/console +38 -0
- data/bin/parse-console +136 -0
- data/bin/server +17 -0
- data/bin/setup +7 -0
- data/config/parse-config.json +12 -0
- data/docs/TEST_SERVER.md +271 -0
- data/docs/_config.yml +1 -0
- data/docs/mcp_guide.md +3484 -0
- data/docs/mongodb_direct_guide.md +1348 -0
- data/docs/mongodb_index_optimization_guide.md +631 -0
- data/examples/transaction_example.rb +219 -0
- data/lib/parse/acl_scope.rb +728 -0
- data/lib/parse/agent/cancellation_token.rb +80 -0
- data/lib/parse/agent/constraint_translator.rb +480 -0
- data/lib/parse/agent/describe.rb +420 -0
- data/lib/parse/agent/errors.rb +133 -0
- data/lib/parse/agent/mcp_client.rb +557 -0
- data/lib/parse/agent/mcp_dispatcher.rb +1023 -0
- data/lib/parse/agent/mcp_rack_app.rb +1143 -0
- data/lib/parse/agent/mcp_server.rb +376 -0
- data/lib/parse/agent/metadata_audit.rb +259 -0
- data/lib/parse/agent/metadata_dsl.rb +733 -0
- data/lib/parse/agent/metadata_registry.rb +794 -0
- data/lib/parse/agent/pipeline_validator.rb +82 -0
- data/lib/parse/agent/prompts.rb +351 -0
- data/lib/parse/agent/rate_limiter.rb +158 -0
- data/lib/parse/agent/relation_graph.rb +162 -0
- data/lib/parse/agent/result_formatter.rb +453 -0
- data/lib/parse/agent/tools.rb +5489 -0
- data/lib/parse/agent.rb +3249 -0
- data/lib/parse/api/aggregate.rb +79 -0
- data/lib/parse/api/all.rb +26 -0
- data/lib/parse/api/analytics.rb +18 -0
- data/lib/parse/api/batch.rb +33 -0
- data/lib/parse/api/cloud_functions.rb +58 -0
- data/lib/parse/api/config.rb +125 -0
- data/lib/parse/api/files.rb +29 -0
- data/lib/parse/api/hooks.rb +117 -0
- data/lib/parse/api/objects.rb +146 -0
- data/lib/parse/api/path_segment.rb +75 -0
- data/lib/parse/api/push.rb +20 -0
- data/lib/parse/api/schema.rb +49 -0
- data/lib/parse/api/server.rb +50 -0
- data/lib/parse/api/sessions.rb +24 -0
- data/lib/parse/api/users.rb +250 -0
- data/lib/parse/atlas_search/index_manager.rb +353 -0
- data/lib/parse/atlas_search/result.rb +204 -0
- data/lib/parse/atlas_search/search_builder.rb +604 -0
- data/lib/parse/atlas_search/session.rb +253 -0
- data/lib/parse/atlas_search.rb +995 -0
- data/lib/parse/client/authentication.rb +97 -0
- data/lib/parse/client/batch.rb +234 -0
- data/lib/parse/client/body_builder.rb +240 -0
- data/lib/parse/client/caching.rb +203 -0
- data/lib/parse/client/logging.rb +293 -0
- data/lib/parse/client/profiling.rb +181 -0
- data/lib/parse/client/protocol.rb +91 -0
- data/lib/parse/client/request.rb +233 -0
- data/lib/parse/client/response.rb +208 -0
- data/lib/parse/client.rb +1104 -0
- data/lib/parse/clp_scope.rb +361 -0
- data/lib/parse/live_query/circuit_breaker.rb +256 -0
- data/lib/parse/live_query/client.rb +1001 -0
- data/lib/parse/live_query/configuration.rb +224 -0
- data/lib/parse/live_query/event.rb +115 -0
- data/lib/parse/live_query/event_queue.rb +272 -0
- data/lib/parse/live_query/health_monitor.rb +214 -0
- data/lib/parse/live_query/logging.rb +149 -0
- data/lib/parse/live_query/subscription.rb +294 -0
- data/lib/parse/live_query.rb +163 -0
- data/lib/parse/lookup_rewriter.rb +445 -0
- data/lib/parse/model/acl.rb +968 -0
- data/lib/parse/model/associations/belongs_to.rb +275 -0
- data/lib/parse/model/associations/collection_proxy.rb +435 -0
- data/lib/parse/model/associations/has_many.rb +597 -0
- data/lib/parse/model/associations/has_one.rb +158 -0
- data/lib/parse/model/associations/pointer_collection_proxy.rb +134 -0
- data/lib/parse/model/associations/relation_collection_proxy.rb +177 -0
- data/lib/parse/model/bytes.rb +62 -0
- data/lib/parse/model/classes/audience.rb +262 -0
- data/lib/parse/model/classes/installation.rb +363 -0
- data/lib/parse/model/classes/job_schedule.rb +153 -0
- data/lib/parse/model/classes/job_status.rb +264 -0
- data/lib/parse/model/classes/product.rb +75 -0
- data/lib/parse/model/classes/push_status.rb +263 -0
- data/lib/parse/model/classes/role.rb +751 -0
- data/lib/parse/model/classes/session.rb +201 -0
- data/lib/parse/model/classes/user.rb +943 -0
- data/lib/parse/model/clp.rb +544 -0
- data/lib/parse/model/core/actions.rb +1268 -0
- data/lib/parse/model/core/builder.rb +139 -0
- data/lib/parse/model/core/create_lock.rb +386 -0
- data/lib/parse/model/core/describe.rb +382 -0
- data/lib/parse/model/core/enhanced_change_tracking.rb +159 -0
- data/lib/parse/model/core/errors.rb +38 -0
- data/lib/parse/model/core/fetching.rb +566 -0
- data/lib/parse/model/core/field_guards.rb +220 -0
- data/lib/parse/model/core/indexing.rb +382 -0
- data/lib/parse/model/core/parse_reference.rb +407 -0
- data/lib/parse/model/core/properties.rb +809 -0
- data/lib/parse/model/core/querying.rb +491 -0
- data/lib/parse/model/core/schema.rb +202 -0
- data/lib/parse/model/core/search_indexing.rb +174 -0
- data/lib/parse/model/date.rb +88 -0
- data/lib/parse/model/email.rb +213 -0
- data/lib/parse/model/file.rb +527 -0
- data/lib/parse/model/geojson.rb +271 -0
- data/lib/parse/model/geopoint.rb +261 -0
- data/lib/parse/model/model.rb +260 -0
- data/lib/parse/model/object.rb +2068 -0
- data/lib/parse/model/phone.rb +520 -0
- data/lib/parse/model/pointer.rb +443 -0
- data/lib/parse/model/polygon.rb +406 -0
- data/lib/parse/model/push.rb +975 -0
- data/lib/parse/model/shortnames.rb +8 -0
- data/lib/parse/model/time_zone.rb +141 -0
- data/lib/parse/model/validations/uniqueness_validator.rb +97 -0
- data/lib/parse/model/validations.rb +96 -0
- data/lib/parse/mongodb.rb +2300 -0
- data/lib/parse/pipeline_security.rb +554 -0
- data/lib/parse/query/constraint.rb +198 -0
- data/lib/parse/query/constraints.rb +3279 -0
- data/lib/parse/query/cursor.rb +434 -0
- data/lib/parse/query/n_plus_one_detector.rb +445 -0
- data/lib/parse/query/operation.rb +104 -0
- data/lib/parse/query/ordering.rb +66 -0
- data/lib/parse/query.rb +7028 -0
- data/lib/parse/schema/index_migrator.rb +291 -0
- data/lib/parse/schema/search_index_migrator.rb +289 -0
- data/lib/parse/schema.rb +494 -0
- data/lib/parse/stack/generators/rails.rb +40 -0
- data/lib/parse/stack/generators/templates/model.erb +51 -0
- data/lib/parse/stack/generators/templates/model_installation.rb +4 -0
- data/lib/parse/stack/generators/templates/model_role.rb +4 -0
- data/lib/parse/stack/generators/templates/model_session.rb +4 -0
- data/lib/parse/stack/generators/templates/model_user.rb +11 -0
- data/lib/parse/stack/generators/templates/parse.rb +12 -0
- data/lib/parse/stack/generators/templates/webhooks.rb +10 -0
- data/lib/parse/stack/railtie.rb +18 -0
- data/lib/parse/stack/tasks.rb +563 -0
- data/lib/parse/stack/version.rb +11 -0
- data/lib/parse/stack.rb +455 -0
- data/lib/parse/two_factor_auth/user_extension.rb +449 -0
- data/lib/parse/two_factor_auth.rb +310 -0
- data/lib/parse/webhooks/payload.rb +360 -0
- data/lib/parse/webhooks/registration.rb +199 -0
- data/lib/parse/webhooks/replay_protection.rb +189 -0
- data/lib/parse/webhooks.rb +510 -0
- data/lib/parse-stack-next.rb +5 -0
- data/lib/parse-stack.rb +5 -0
- data/parse-stack-next.gemspec +82 -0
- data/parse-stack.png +0 -0
- data/scripts/debug-ips.js +35 -0
- data/scripts/docker/Dockerfile.parse +13 -0
- data/scripts/docker/atlas-init.js +284 -0
- data/scripts/docker/docker-compose.atlas.yml +76 -0
- data/scripts/docker/docker-compose.test.yml +106 -0
- data/scripts/docker/mongo-init.js +21 -0
- data/scripts/eval_mcp_with_lm_studio.rb +274 -0
- data/scripts/start-parse.sh +90 -0
- data/scripts/start_mcp_server.rb +78 -0
- data/scripts/test_server_connection.rb +82 -0
- metadata +377 -0
data/lib/parse/client.rb
ADDED
|
@@ -0,0 +1,1104 @@
|
|
|
1
|
+
require "faraday"
|
|
2
|
+
|
|
3
|
+
# Attempt to load the persistent connection adapter for better performance.
|
|
4
|
+
# Falls back gracefully to the default adapter if not available.
|
|
5
|
+
NET_HTTP_PERSISTENT_AVAILABLE = begin
|
|
6
|
+
require "faraday/net_http_persistent"
|
|
7
|
+
true
|
|
8
|
+
rescue LoadError
|
|
9
|
+
warn "[parse-stack] faraday-net_http_persistent gem not available. " \
|
|
10
|
+
"Using standard Net::HTTP adapter. For better performance, add " \
|
|
11
|
+
"'faraday-net_http_persistent' to your Gemfile."
|
|
12
|
+
false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
require "active_support"
|
|
16
|
+
require "moneta"
|
|
17
|
+
require "active_model/serialization"
|
|
18
|
+
require "active_model/serializers/json"
|
|
19
|
+
require "active_support/inflector"
|
|
20
|
+
require "active_support/core_ext/object"
|
|
21
|
+
require "active_support/core_ext/string"
|
|
22
|
+
require "active_support/core_ext/date/calculations"
|
|
23
|
+
require "active_support/core_ext/date_time/calculations"
|
|
24
|
+
require "active_support/core_ext/time/calculations"
|
|
25
|
+
require "active_support/core_ext"
|
|
26
|
+
require_relative "client/request"
|
|
27
|
+
require_relative "client/response"
|
|
28
|
+
require_relative "client/batch"
|
|
29
|
+
require_relative "client/body_builder"
|
|
30
|
+
require_relative "client/authentication"
|
|
31
|
+
require_relative "client/caching"
|
|
32
|
+
require_relative "client/logging"
|
|
33
|
+
require_relative "client/profiling"
|
|
34
|
+
require_relative "api/all"
|
|
35
|
+
|
|
36
|
+
module Parse
|
|
37
|
+
class Error < StandardError
|
|
38
|
+
# An error when a general connection occurs.
|
|
39
|
+
class ConnectionError < Error; end
|
|
40
|
+
|
|
41
|
+
# An error when a connection timeout occurs.
|
|
42
|
+
class TimeoutError < Error; end
|
|
43
|
+
|
|
44
|
+
# An error when there is an Parse REST API protocol error.
|
|
45
|
+
class ProtocolError < Error; end
|
|
46
|
+
|
|
47
|
+
# An error when the Parse server returned invalid code.
|
|
48
|
+
class ServerError < Error; end
|
|
49
|
+
|
|
50
|
+
# An error when a Parse server responds with HTTP 500.
|
|
51
|
+
class ServiceUnavailableError < Error; end
|
|
52
|
+
|
|
53
|
+
# An error when the authentication credentials in the request are invalid.
|
|
54
|
+
class AuthenticationError < Error; end
|
|
55
|
+
|
|
56
|
+
# An error when the burst limit has been exceeded.
|
|
57
|
+
class RequestLimitExceededError < Error; end
|
|
58
|
+
|
|
59
|
+
# An error when the session token provided in the request is invalid.
|
|
60
|
+
class InvalidSessionTokenError < Error; end
|
|
61
|
+
|
|
62
|
+
# An error raised when a cloud function or job returns an error response
|
|
63
|
+
# (e.g. when the cloud code calls error!()). Carries the function name,
|
|
64
|
+
# Parse error code, HTTP status, and the underlying Response for debugging.
|
|
65
|
+
class CloudCodeError < Error
|
|
66
|
+
attr_reader :function_name, :code, :http_status, :response
|
|
67
|
+
|
|
68
|
+
def initialize(function_name, response)
|
|
69
|
+
@function_name = function_name
|
|
70
|
+
@response = response
|
|
71
|
+
@code = response.code
|
|
72
|
+
@http_status = response.http_status
|
|
73
|
+
super("Parse cloud function `#{function_name}` failed: [#{@code}] #{response.error} (HTTP #{@http_status})")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def inspect
|
|
77
|
+
"#<#{self.class} function=#{@function_name.inspect} code=#{@code.inspect} http_status=#{@http_status.inspect}>"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Retrieve the App specific Parse configuration parameters. The configuration
|
|
83
|
+
# for a connection is cached after the first request. Use the bang version to
|
|
84
|
+
# force update from the Parse backend.
|
|
85
|
+
# @example
|
|
86
|
+
# val = Parse.config["myKey"]
|
|
87
|
+
# val = Parse.config["myKey"] # cached
|
|
88
|
+
# @see Parse.config!
|
|
89
|
+
# @param conn [Symbol] the name of the client connection to use.
|
|
90
|
+
# @return [Hash] the Parse config hash for the session.
|
|
91
|
+
def self.config(conn = :default)
|
|
92
|
+
Parse::Client.client(conn).config
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Set a parameter in the Parse configuration for an application.
|
|
96
|
+
# @example
|
|
97
|
+
# # update a config with Parse
|
|
98
|
+
# Parse.set_config "myKey", "someValue"
|
|
99
|
+
# # mark a single key as master-key-only
|
|
100
|
+
# Parse.set_config "myKey", "someValue", master_key_only: true
|
|
101
|
+
# @param field [String] the name configuration variable.
|
|
102
|
+
# @param value [Object] the value configuration variable. Only Parse types are supported.
|
|
103
|
+
# @param conn [Symbol] the name of the client connection to use.
|
|
104
|
+
# @param master_key_only [Boolean, nil] when not nil, sets the masterKeyOnly
|
|
105
|
+
# flag for `field` to the given boolean value in the same request.
|
|
106
|
+
# @return [Hash] the Parse config hash for the session.
|
|
107
|
+
def self.set_config(field, value, conn = :default, master_key_only: nil)
|
|
108
|
+
opts = master_key_only.nil? ? {} : { master_key_only: { field.to_s => !!master_key_only } }
|
|
109
|
+
Parse::Client.client(conn).update_config({ field => value }, **opts)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Set a key value pairs in the Parse configuration for an application.
|
|
113
|
+
# @example
|
|
114
|
+
# # batch update several
|
|
115
|
+
# Parse.update_config({fieldEnabled: true, searchMiles: 50})
|
|
116
|
+
# # also mark some keys as master-key-only
|
|
117
|
+
# Parse.update_config({fieldEnabled: true}, master_key_only: { fieldEnabled: true })
|
|
118
|
+
# @param params [Hash] a set of key value pairs to set in the Parse configuration.
|
|
119
|
+
# @param conn [Symbol] the name of the client connection to use.
|
|
120
|
+
# @param master_key_only [Hash{String=>Boolean}, nil] optional map of config
|
|
121
|
+
# keys to boolean masterKeyOnly flags. Parse Server merges this with any
|
|
122
|
+
# existing masterKeyOnly settings; unspecified keys keep their current flag.
|
|
123
|
+
# @return [Hash] the Parse config hash for the session.
|
|
124
|
+
def self.update_config(params, conn = :default, master_key_only: nil)
|
|
125
|
+
opts = master_key_only.nil? ? {} : { master_key_only: master_key_only }
|
|
126
|
+
Parse::Client.client(conn).update_config(params, **opts)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Force fetch updated Parse configuration
|
|
130
|
+
# @param conn [Symbol] the name of the client connection to use.
|
|
131
|
+
# @return [Hash] the Parse configuration
|
|
132
|
+
def self.config!(conn = :default)
|
|
133
|
+
Parse::Client.client(conn).config!
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Return every config entry zipped with its masterKeyOnly trait.
|
|
137
|
+
# @example
|
|
138
|
+
# Parse.config_entries
|
|
139
|
+
# # => { "fieldA" => { value: "x", master_key_only: false } }
|
|
140
|
+
# Parse.config_entries(master: true)
|
|
141
|
+
# # => { "fieldA" => { value: "x", master_key_only: false },
|
|
142
|
+
# # "fieldB" => { value: 42, master_key_only: true } }
|
|
143
|
+
# @param conn [Symbol] the name of the client connection to use.
|
|
144
|
+
# @param master [Boolean] when true, include master-key-only entries.
|
|
145
|
+
# @return [Hash{String=>Hash}] map of config key to `{value:, master_key_only:}`.
|
|
146
|
+
def self.config_entries(conn = :default, master: false)
|
|
147
|
+
Parse::Client.client(conn).config_entries(master: master)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Retrieve the masterKeyOnly flag map for the application configuration.
|
|
151
|
+
# @example
|
|
152
|
+
# Parse.master_key_only["secretKey"] # => true
|
|
153
|
+
# @param conn [Symbol] the name of the client connection to use.
|
|
154
|
+
# @return [Hash{String=>Boolean}] map of config keys to masterKeyOnly flags,
|
|
155
|
+
# or an empty hash if the server did not return one.
|
|
156
|
+
def self.master_key_only(conn = :default)
|
|
157
|
+
Parse::Client.client(conn).master_key_only
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Helper method to get the default Parse client.
|
|
161
|
+
# @param conn [Symbol] the name of the client connection to use. Defaults to :default
|
|
162
|
+
# @return [Parse::Client] a client object for the connection name.
|
|
163
|
+
def self.client(conn = :default)
|
|
164
|
+
Parse::Client.client(conn)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# The shared cache for the default client connection. This is useful if you want to
|
|
168
|
+
# also utilize the same cache store for other purposes in your application.
|
|
169
|
+
# This should normally be a {https://github.com/minad/moneta Moneta} unified
|
|
170
|
+
# cache interface.
|
|
171
|
+
# @return [Moneta::Transformer,Moneta::Expires] the cache instance
|
|
172
|
+
# @see Parse::Client#cache
|
|
173
|
+
def self.cache
|
|
174
|
+
@shared_cache ||= Parse::Client.client(:default).cache
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# This class is the core and low level API for the Parse SDK REST interface that
|
|
178
|
+
# is used by the other components. It can manage multiple sessions, which means
|
|
179
|
+
# you can have multiple client instances pointing to different Parse Applications
|
|
180
|
+
# at the same time. It handles sending raw requests as well as providing
|
|
181
|
+
# Request/Response objects for all API handlers. The connection engine is
|
|
182
|
+
# Faraday, which means it is open to add any additional middleware for
|
|
183
|
+
# features you'd like to implement.
|
|
184
|
+
class Client
|
|
185
|
+
include Parse::API::Analytics
|
|
186
|
+
include Parse::API::Aggregate
|
|
187
|
+
include Parse::API::Batch
|
|
188
|
+
include Parse::API::CloudFunctions
|
|
189
|
+
include Parse::API::Config
|
|
190
|
+
include Parse::API::Files
|
|
191
|
+
include Parse::API::Hooks
|
|
192
|
+
include Parse::API::Objects
|
|
193
|
+
include Parse::API::Push
|
|
194
|
+
include Parse::API::Schema
|
|
195
|
+
include Parse::API::Server
|
|
196
|
+
include Parse::API::Sessions
|
|
197
|
+
include Parse::API::Users
|
|
198
|
+
# The user agent header key.
|
|
199
|
+
USER_AGENT_HEADER = "User-Agent".freeze
|
|
200
|
+
# The value for the User-Agent header.
|
|
201
|
+
USER_AGENT_VERSION = "Parse-Stack v#{Parse::Stack::VERSION}".freeze
|
|
202
|
+
# The default retry count
|
|
203
|
+
DEFAULT_RETRIES = 2
|
|
204
|
+
# The wait time in seconds between retries
|
|
205
|
+
RETRY_DELAY = 1.5
|
|
206
|
+
|
|
207
|
+
# An error when a general response error occurs when communicating with Parse server.
|
|
208
|
+
class ResponseError < Parse::Error; end
|
|
209
|
+
|
|
210
|
+
# An error when a Parse server response carries code 137 (DuplicateValue),
|
|
211
|
+
# typically raised when a unique field (or MongoDB unique index) rejects an
|
|
212
|
+
# insert. Carries the {Parse::Response} for inspection. The synchronize-create
|
|
213
|
+
# wrapper in {Parse::Core::Actions} rescues this internally and re-queries
|
|
214
|
+
# inside the held lock to return the winning object.
|
|
215
|
+
#
|
|
216
|
+
# **Message redaction.** Parse Server (and the underlying MongoDB driver)
|
|
217
|
+
# serialize the offending unique-key payload into the error string in two
|
|
218
|
+
# parallel forms: `keyValue: { "email": "user@example.com" }` AND
|
|
219
|
+
# `dup key: { : "user@example.com" }`. Echoing either into application
|
|
220
|
+
# logs exposes the colliding identifier (email, username, account number,
|
|
221
|
+
# external ID) to anyone with log access — turning a duplicate-write
|
|
222
|
+
# error into a unique-field enumeration oracle. The constructor strips
|
|
223
|
+
# both fragments before delegating to `super`. The raw response is
|
|
224
|
+
# preserved on `#response` for callers that legitimately need the
|
|
225
|
+
# unredacted detail (e.g. the synchronize-create wrapper).
|
|
226
|
+
class DuplicateValueError < ResponseError
|
|
227
|
+
CODE = 137
|
|
228
|
+
# Matches both MongoDB E11000 fragment forms: `keyValue: { ... }`
|
|
229
|
+
# and `dup key: { ... }`. The driver emits the offending unique-key
|
|
230
|
+
# value verbatim in each, so both must be stripped to close the leak.
|
|
231
|
+
KEY_VALUE_PATTERN = /(?:keyValue|dup\s*key)\s*:?\s*\{[^}]*\}/i.freeze
|
|
232
|
+
REDACTION = "[REDACTED]".freeze
|
|
233
|
+
|
|
234
|
+
attr_reader :response
|
|
235
|
+
|
|
236
|
+
def initialize(response = nil)
|
|
237
|
+
@response = response
|
|
238
|
+
raw = if response.is_a?(String)
|
|
239
|
+
response
|
|
240
|
+
elsif response.respond_to?(:error)
|
|
241
|
+
response.error
|
|
242
|
+
else
|
|
243
|
+
response.to_s
|
|
244
|
+
end
|
|
245
|
+
super(self.class.redact(raw))
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Strip `keyValue: { ... }` fragments from a message string so the
|
|
249
|
+
# offending unique-constraint value never leaks into log lines.
|
|
250
|
+
# Returns the original message verbatim when it contains no
|
|
251
|
+
# `keyValue:` token, so non-MongoDB-shaped errors are unaffected.
|
|
252
|
+
# @param msg [String, nil]
|
|
253
|
+
# @return [String, nil]
|
|
254
|
+
def self.redact(msg)
|
|
255
|
+
return msg if msg.nil?
|
|
256
|
+
s = msg.to_s
|
|
257
|
+
s.gsub(KEY_VALUE_PATTERN, REDACTION)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# @!attribute cache
|
|
262
|
+
# The underlying cache store for caching API requests.
|
|
263
|
+
# @see Parse.cache
|
|
264
|
+
# @return [Moneta::Transformer,Moneta::Expires]
|
|
265
|
+
# @!attribute [r] application_id
|
|
266
|
+
# The Parse application identifier to be sent in every API request.
|
|
267
|
+
# @return [String]
|
|
268
|
+
# @!attribute [r] api_key
|
|
269
|
+
# The Parse API key to be sent in every API request.
|
|
270
|
+
# @return [String]
|
|
271
|
+
# @!attribute [r] master_key
|
|
272
|
+
# The Parse master key for this application, which when set, will be sent
|
|
273
|
+
# in every API request. (There is a way to prevent this on a per request basis.)
|
|
274
|
+
# @return [String]
|
|
275
|
+
# @!attribute [r] server_url
|
|
276
|
+
# The Parse server url that will be receiving these API requests. By default
|
|
277
|
+
# this will be {Parse::Protocol::SERVER_URL}.
|
|
278
|
+
# @return [String]
|
|
279
|
+
# @!attribute retry_limit
|
|
280
|
+
# The default retry count for the client when a specific request timesout or
|
|
281
|
+
# the service is unavailable. Defaults to {DEFAULT_RETRIES}.
|
|
282
|
+
# @return [String]
|
|
283
|
+
attr_accessor :cache
|
|
284
|
+
attr_writer :retry_limit
|
|
285
|
+
attr_reader :application_id, :api_key, :master_key, :server_url
|
|
286
|
+
alias_method :app_id, :application_id
|
|
287
|
+
# The client can support multiple sessions. The first session created, will be placed
|
|
288
|
+
# under the default session tag. The :default session will be the default client to be used
|
|
289
|
+
# by the other classes including Parse::Query and Parse::Objects
|
|
290
|
+
@clients = { default: nil }
|
|
291
|
+
class << self
|
|
292
|
+
# @!attribute [r] clients
|
|
293
|
+
# A hash of Parse::Client instances.
|
|
294
|
+
# @return [Hash<Parse::Client>]
|
|
295
|
+
attr_reader :clients
|
|
296
|
+
|
|
297
|
+
# @param conn [Symbol] the name of the connection.
|
|
298
|
+
# @return [Boolean] true if a Parse::Client has been configured.
|
|
299
|
+
def client?(conn = :default)
|
|
300
|
+
@clients[conn].present?
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Returns or create a new Parse::Client connection for the given connection
|
|
304
|
+
# name.
|
|
305
|
+
# @param conn [Symbol] the name of the connection.
|
|
306
|
+
# @return [Parse::Client]
|
|
307
|
+
def client(conn = :default)
|
|
308
|
+
@clients[conn] ||= self.new
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# Setup the a new client with the appropriate Parse app keys, middleware and
|
|
312
|
+
# options.
|
|
313
|
+
# @example
|
|
314
|
+
# Parse.setup app_id: "YOUR_APP_ID",
|
|
315
|
+
# api_key: "YOUR_REST_API_KEY",
|
|
316
|
+
# master_key: "YOUR_MASTER_KEY", # optional
|
|
317
|
+
# server_url: 'https://localhost:1337/parse' #default
|
|
318
|
+
# @param opts (see Parse::Client#initialize)
|
|
319
|
+
# @option opts (see Parse::Client#initialize)
|
|
320
|
+
# @yield the block for additional configuration with Faraday middleware.
|
|
321
|
+
# @return (see Parse::Client#initialize)
|
|
322
|
+
# @see Parse::Middleware::BodyBuilder
|
|
323
|
+
# @see Parse::Middleware::Caching
|
|
324
|
+
# @see Parse::Middleware::Authentication
|
|
325
|
+
# @see Parse::Protocol
|
|
326
|
+
def setup(opts = {}, &block)
|
|
327
|
+
@clients[:default] = self.new(opts, &block)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# @!visibility private
|
|
331
|
+
# Emit a redacted warning about a Parse::Response error to stderr.
|
|
332
|
+
#
|
|
333
|
+
# Routes the response error string through
|
|
334
|
+
# {Parse::Middleware::BodyBuilder.redact} to strip credentials (passwords,
|
|
335
|
+
# tokens, sessionTokens, access_tokens, authData) before logging, and
|
|
336
|
+
# truncates to {SAFE_WARN_MAX_ERROR_LENGTH} chars.
|
|
337
|
+
#
|
|
338
|
+
# @param tag [String] the bracketed prefix (e.g. "AuthenticationError").
|
|
339
|
+
# @param response [Parse::Response] the response carrying the error.
|
|
340
|
+
# @param name [String, nil] optional cloud-function or job name for context.
|
|
341
|
+
# @return [nil]
|
|
342
|
+
def _safe_warn(tag, response, name: nil)
|
|
343
|
+
err = Parse::Middleware::BodyBuilder.redact(response.error.to_s)[0, SAFE_WARN_MAX_ERROR_LENGTH]
|
|
344
|
+
if name
|
|
345
|
+
warn "[Parse:#{tag}] `#{name}` [#{response.code}] #{err} (HTTP #{response.http_status})"
|
|
346
|
+
else
|
|
347
|
+
warn "[Parse:#{tag}] [E-#{response.code}] #{response.request} : #{err} (#{response.http_status})"
|
|
348
|
+
end
|
|
349
|
+
nil
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# @!visibility private
|
|
354
|
+
# Maximum number of characters of a Parse::Response error string to include
|
|
355
|
+
# in safe warn output. Bounds log volume from chatty server errors or
|
|
356
|
+
# misbehaving cloud functions.
|
|
357
|
+
SAFE_WARN_MAX_ERROR_LENGTH = 200
|
|
358
|
+
|
|
359
|
+
# Create a new client connected to the Parse Server REST API endpoint.
|
|
360
|
+
# @param opts [Hash] a set of connection options to configure the client.
|
|
361
|
+
# @option opts [String] :server_url The server url of your Parse Server if you
|
|
362
|
+
# are not using the hosted Parse service. By default it will use
|
|
363
|
+
# ENV["PARSE_SERVER_URL"] if available, otherwise fallback to {Parse::Protocol::SERVER_URL}.
|
|
364
|
+
# @option opts [String] :app_id The Parse application id. Defaults to
|
|
365
|
+
# ENV['PARSE_SERVER_APPLICATION_ID'].
|
|
366
|
+
# @option opts [String] :api_key Your Parse REST API Key. Defaults to ENV['PARSE_SERVER_REST_API_KEY'].
|
|
367
|
+
# @option opts [String] :master_key The Parse application master key (optional).
|
|
368
|
+
# If this key is set, it will be sent on every request sent by the client
|
|
369
|
+
# and your models. Defaults to ENV['PARSE_SERVER_MASTER_KEY'].
|
|
370
|
+
# @option opts [Boolean, Symbol] :logging Controls request/response logging.
|
|
371
|
+
# - `true` - Enable logging at :info level
|
|
372
|
+
# - `:debug` - Enable verbose logging with headers and body content
|
|
373
|
+
# - `:warn` - Only log errors and warnings
|
|
374
|
+
# - `false` or `nil` - Disable logging (default)
|
|
375
|
+
# This configures both the new {Parse::Middleware::Logging} middleware
|
|
376
|
+
# and the legacy {Parse::Middleware::BodyBuilder} logging.
|
|
377
|
+
# @option opts [Logger] :logger A custom logger instance for request/response logging.
|
|
378
|
+
# Defaults to Logger.new(STDOUT) if not specified.
|
|
379
|
+
# @option opts [Object] :adapter The connection adapter. By default it uses
|
|
380
|
+
# `:net_http_persistent` for connection pooling. Set `connection_pooling: false`
|
|
381
|
+
# to use the standard `Faraday.default_adapter` (Net/HTTP) instead.
|
|
382
|
+
# @option opts [Boolean, Hash] :connection_pooling Controls HTTP connection pooling.
|
|
383
|
+
# Defaults to `true`, using the `:net_http_persistent` adapter for improved
|
|
384
|
+
# performance through connection reuse. Set to `false` to disable pooling
|
|
385
|
+
# and create a new connection for each request. This option is ignored if
|
|
386
|
+
# `:adapter` is explicitly specified.
|
|
387
|
+
# Pass a Hash to enable pooling with custom configuration:
|
|
388
|
+
# - `:pool_size` [Integer] - Number of connections per thread (default: 1)
|
|
389
|
+
# - `:idle_timeout` [Integer] - Seconds before closing idle connections (default: 5)
|
|
390
|
+
# - `:keep_alive` [Integer] - HTTP Keep-Alive timeout in seconds
|
|
391
|
+
# @example Custom connection pooling
|
|
392
|
+
# Parse.setup(
|
|
393
|
+
# connection_pooling: { pool_size: 5, idle_timeout: 60, keep_alive: 60 }
|
|
394
|
+
# )
|
|
395
|
+
# @option opts [Moneta::Transformer,Moneta::Expires] :cache A caching adapter of type
|
|
396
|
+
# {https://github.com/minad/moneta Moneta::Transformer} or
|
|
397
|
+
# {https://github.com/minad/moneta Moneta::Expires} that will be used
|
|
398
|
+
# by the caching middleware {Parse::Middleware::Caching}.
|
|
399
|
+
# Caching queries and object fetches can help improve the performance of
|
|
400
|
+
# your application, even if it is for a few seconds. Only successful GET
|
|
401
|
+
# object fetches and non-empty result queries will be cached by default.
|
|
402
|
+
# You may set the default expiration time with the expires option.
|
|
403
|
+
# At any point in time you may clear the cache by calling the {Parse::Client#clear_cache!}
|
|
404
|
+
# method on the client connection. See {https://github.com/minad/moneta Moneta}.
|
|
405
|
+
# @option opts [Integer] :expires Sets the default cache expiration time
|
|
406
|
+
# (in seconds) for successful non-empty GET requests when using the caching
|
|
407
|
+
# middleware. The default value is 3 seconds. If :expires is set to 0,
|
|
408
|
+
# caching will be disabled. You can always clear the current state of the
|
|
409
|
+
# cache using the clear_cache! method on your Parse::Client instance.
|
|
410
|
+
# @option opts [Hash] :faraday You may pass a hash of options that will be
|
|
411
|
+
# passed to the Faraday constructor.
|
|
412
|
+
# @option opts [String] :live_query_url The WebSocket URL for Parse LiveQuery server
|
|
413
|
+
# (e.g., "wss://your-parse-server.com"). If not specified, falls back to
|
|
414
|
+
# ENV["PARSE_LIVE_QUERY_URL"]. LiveQuery enables real-time subscriptions
|
|
415
|
+
# to changes in Parse objects.
|
|
416
|
+
# @example Enable LiveQuery
|
|
417
|
+
# Parse.setup(
|
|
418
|
+
# server_url: "https://your-server.com/parse",
|
|
419
|
+
# application_id: "YOUR_APP_ID",
|
|
420
|
+
# api_key: "YOUR_API_KEY",
|
|
421
|
+
# live_query_url: "wss://your-server.com"
|
|
422
|
+
# )
|
|
423
|
+
# @option opts [Hash] :live_query Advanced LiveQuery configuration options.
|
|
424
|
+
# Pass a hash with custom settings for the LiveQuery client.
|
|
425
|
+
# - :url [String] - WebSocket URL (alternative to :live_query_url)
|
|
426
|
+
# - :auto_reconnect [Boolean] - Auto-reconnect on disconnect (default: true)
|
|
427
|
+
# @raise Parse::Error::ConnectionError if the client was not properly configured with required keys or url.
|
|
428
|
+
# @raise ArgumentError if the cache instance passed to the :cache option is not of Moneta::Transformer or Moneta::Expires
|
|
429
|
+
# @see Parse::Middleware::BodyBuilder
|
|
430
|
+
# @see Parse::Middleware::Caching
|
|
431
|
+
# @see Parse::Middleware::Authentication
|
|
432
|
+
# @see Parse::Protocol
|
|
433
|
+
def initialize(opts = {})
|
|
434
|
+
@server_url = opts[:server_url] || ENV["PARSE_SERVER_URL"] || Parse::Protocol::SERVER_URL
|
|
435
|
+
@application_id = opts[:application_id] || opts[:app_id] || ENV["PARSE_SERVER_APPLICATION_ID"] || ENV["PARSE_APP_ID"]
|
|
436
|
+
@api_key = opts[:api_key] || opts[:rest_api_key] || ENV["PARSE_SERVER_REST_API_KEY"] || ENV["PARSE_API_KEY"]
|
|
437
|
+
@master_key = opts[:master_key] || ENV["PARSE_SERVER_MASTER_KEY"] || ENV["PARSE_MASTER_KEY"]
|
|
438
|
+
|
|
439
|
+
@require_https = opts.fetch(:require_https, ENV["PARSE_REQUIRE_HTTPS"] == "true")
|
|
440
|
+
@allow_faraday_proxy = opts.fetch(:allow_faraday_proxy, false)
|
|
441
|
+
|
|
442
|
+
# Security check for HTTP usage (except localhost/127.0.0.1 for development)
|
|
443
|
+
if @server_url&.start_with?("http://") && !@server_url.match?(%r{^http://(localhost|127\.0\.0\.1)(:|/)})
|
|
444
|
+
if @require_https
|
|
445
|
+
raise ArgumentError, "[Parse::Client] HTTPS required but server URL uses HTTP: #{@server_url}. " \
|
|
446
|
+
"Set require_https: false or use an HTTPS URL."
|
|
447
|
+
else
|
|
448
|
+
warn "[Parse::Client] SECURITY WARNING: Using HTTP instead of HTTPS for Parse server. " \
|
|
449
|
+
"This exposes credentials and data to network interception. " \
|
|
450
|
+
"Use HTTPS in production: #{@server_url}"
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
# Determine the HTTP adapter to use
|
|
455
|
+
# Priority: explicit :adapter > :connection_pooling setting > default (pooling enabled)
|
|
456
|
+
# Falls back to default adapter if net_http_persistent is not available
|
|
457
|
+
if opts[:adapter]
|
|
458
|
+
# User explicitly specified an adapter, use it directly
|
|
459
|
+
adapter = opts[:adapter]
|
|
460
|
+
adapter_options = {}
|
|
461
|
+
elsif opts[:connection_pooling] == false
|
|
462
|
+
# User explicitly disabled connection pooling
|
|
463
|
+
adapter = Faraday.default_adapter
|
|
464
|
+
adapter_options = {}
|
|
465
|
+
elsif opts[:connection_pooling].is_a?(Hash)
|
|
466
|
+
# User provided connection pooling with custom options
|
|
467
|
+
if NET_HTTP_PERSISTENT_AVAILABLE
|
|
468
|
+
adapter = :net_http_persistent
|
|
469
|
+
adapter_options = opts[:connection_pooling]
|
|
470
|
+
else
|
|
471
|
+
adapter = Faraday.default_adapter
|
|
472
|
+
adapter_options = {}
|
|
473
|
+
end
|
|
474
|
+
else
|
|
475
|
+
# Default: use persistent connections for better performance (if available)
|
|
476
|
+
if NET_HTTP_PERSISTENT_AVAILABLE
|
|
477
|
+
adapter = :net_http_persistent
|
|
478
|
+
adapter_options = {}
|
|
479
|
+
else
|
|
480
|
+
adapter = Faraday.default_adapter
|
|
481
|
+
adapter_options = {}
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
opts[:expires] ||= 3
|
|
486
|
+
if @server_url.nil? || @application_id.nil? || (@api_key.nil? && @master_key.nil?)
|
|
487
|
+
raise Parse::Error::ConnectionError, "Please call Parse.setup(server_url:, application_id:, api_key:) to setup a client"
|
|
488
|
+
end
|
|
489
|
+
@server_url += "/" unless @server_url.ends_with?("/")
|
|
490
|
+
|
|
491
|
+
# Resolve timeouts. Defaults guard the calling thread against an
|
|
492
|
+
# unresponsive Parse Server (slowloris, hung dyno) which would
|
|
493
|
+
# otherwise tie up Puma/Sidekiq workers indefinitely.
|
|
494
|
+
open_timeout = opts.fetch(:open_timeout, (ENV["PARSE_OPEN_TIMEOUT"] || 5).to_i)
|
|
495
|
+
read_timeout = opts.fetch(:timeout, (ENV["PARSE_TIMEOUT"] || 30).to_i)
|
|
496
|
+
|
|
497
|
+
#Configure Faraday
|
|
498
|
+
opts[:faraday] ||= {}
|
|
499
|
+
# Guard against silent TLS downgrade or attacker-controlled proxy via
|
|
500
|
+
# opts[:faraday]. The require_https check earlier only inspects the URL
|
|
501
|
+
# scheme; without this guard a caller passing
|
|
502
|
+
# faraday: { ssl: { verify: false }, proxy: "http://attacker" }
|
|
503
|
+
# would neuter TLS verification on an HTTPS connection.
|
|
504
|
+
validate_faraday_opts!(opts[:faraday])
|
|
505
|
+
opts[:faraday].merge!(:url => @server_url)
|
|
506
|
+
@conn = Faraday.new(opts[:faraday]) do |conn|
|
|
507
|
+
# Apply timeouts before any user-supplied middleware sees a request.
|
|
508
|
+
conn.options.timeout = read_timeout if read_timeout > 0
|
|
509
|
+
conn.options.open_timeout = open_timeout if open_timeout > 0
|
|
510
|
+
#conn.request :json
|
|
511
|
+
|
|
512
|
+
# Configure logging if enabled
|
|
513
|
+
if opts[:logging].present?
|
|
514
|
+
# Configure the new structured logging middleware
|
|
515
|
+
Parse::Middleware::Logging.enabled = true
|
|
516
|
+
Parse::Middleware::Logging.logger = opts[:logger] if opts[:logger]
|
|
517
|
+
case opts[:logging]
|
|
518
|
+
when :debug
|
|
519
|
+
Parse::Middleware::Logging.log_level = :debug
|
|
520
|
+
Parse::Middleware::BodyBuilder.logging = true
|
|
521
|
+
when :warn
|
|
522
|
+
Parse::Middleware::Logging.log_level = :warn
|
|
523
|
+
else
|
|
524
|
+
Parse::Middleware::Logging.log_level = :info
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
# This middleware handles sending the proper authentication headers to Parse
|
|
529
|
+
# on each request.
|
|
530
|
+
|
|
531
|
+
# this is the required authentication middleware. Should be the first thing
|
|
532
|
+
# so that other middlewares have access to the env that is being set by
|
|
533
|
+
# this middleware. First added is first to brocess.
|
|
534
|
+
conn.use Parse::Middleware::Authentication,
|
|
535
|
+
application_id: @application_id,
|
|
536
|
+
master_key: @master_key,
|
|
537
|
+
api_key: @api_key
|
|
538
|
+
# Request/response logging middleware (configured via Parse.logging_enabled)
|
|
539
|
+
conn.use Parse::Middleware::Logging
|
|
540
|
+
|
|
541
|
+
# Performance profiling middleware (configured via Parse.profiling_enabled)
|
|
542
|
+
conn.use Parse::Middleware::Profiling
|
|
543
|
+
|
|
544
|
+
# This middleware turns the result from Parse into a Parse::Response object
|
|
545
|
+
# and making sure request that are going out, follow the proper MIME format.
|
|
546
|
+
# We place it after the Authentication middleware in case we need to use then
|
|
547
|
+
# authentication information when building request and responses.
|
|
548
|
+
conn.use Parse::Middleware::BodyBuilder
|
|
549
|
+
|
|
550
|
+
if opts[:cache].present?
|
|
551
|
+
if opts[:expires].to_i <= 0
|
|
552
|
+
warn "[Parse::Client] Cache store provided but :expires is not set or is 0. " \
|
|
553
|
+
"Caching will be disabled. Set :expires to enable caching (e.g., expires: 10)."
|
|
554
|
+
else
|
|
555
|
+
# advanced: provide a REDIS url, we'll configure a Moneta Redis store.
|
|
556
|
+
if opts[:cache].is_a?(String) && opts[:cache].starts_with?("redis://")
|
|
557
|
+
begin
|
|
558
|
+
opts[:cache] = Moneta.new(:Redis, url: opts[:cache])
|
|
559
|
+
rescue LoadError
|
|
560
|
+
puts "[Parse::Middleware::Caching] Did you forget to load the redis gem (Gemfile)?"
|
|
561
|
+
raise
|
|
562
|
+
end
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
unless [:key?, :[], :delete, :store].all? { |method| opts[:cache].respond_to?(method) }
|
|
566
|
+
raise ArgumentError, "Parse::Client option :cache needs to be a type of Moneta store"
|
|
567
|
+
end
|
|
568
|
+
self.cache = opts[:cache]
|
|
569
|
+
conn.use Parse::Middleware::Caching, self.cache, { expires: opts[:expires].to_i }
|
|
570
|
+
|
|
571
|
+
# Inform about opt-in cache behavior
|
|
572
|
+
unless Parse.default_query_cache
|
|
573
|
+
warn "[Parse::Client] Caching middleware enabled (expires: #{opts[:expires]}s). " \
|
|
574
|
+
"Queries do NOT use cache by default. Use `cache: true` on queries to opt-in, " \
|
|
575
|
+
"or set `Parse.default_query_cache = true` for opt-out behavior."
|
|
576
|
+
end
|
|
577
|
+
end
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
yield(conn) if block_given?
|
|
581
|
+
|
|
582
|
+
# Configure the adapter with optional settings
|
|
583
|
+
# For net_http_persistent:
|
|
584
|
+
# - pool_size must be passed as an adapter argument (constructor param, no setter)
|
|
585
|
+
# - idle_timeout and keep_alive have setters and are configured in the block
|
|
586
|
+
if adapter_options.any?
|
|
587
|
+
# Extract constructor arguments for the adapter
|
|
588
|
+
adapter_args = {}
|
|
589
|
+
adapter_args[:pool_size] = adapter_options[:pool_size] if adapter_options[:pool_size]
|
|
590
|
+
|
|
591
|
+
conn.adapter adapter, **adapter_args do |http|
|
|
592
|
+
http.idle_timeout = adapter_options[:idle_timeout] if adapter_options[:idle_timeout]
|
|
593
|
+
http.keep_alive = adapter_options[:keep_alive] if adapter_options[:keep_alive]
|
|
594
|
+
end
|
|
595
|
+
else
|
|
596
|
+
conn.adapter adapter
|
|
597
|
+
end
|
|
598
|
+
end
|
|
599
|
+
# Faraday's constructor may still synthesise a ProxyOptions from
|
|
600
|
+
# HTTPS_PROXY/HTTP_PROXY env vars regardless of the `proxy: nil`
|
|
601
|
+
# we pass in opts. Clear the proxy on the connection itself to be
|
|
602
|
+
# sure no env-derived MITM survives.
|
|
603
|
+
@conn.proxy = nil if !@allow_faraday_proxy && @conn.respond_to?(:proxy=)
|
|
604
|
+
Parse::Client.clients[:default] ||= self
|
|
605
|
+
|
|
606
|
+
# Configure LiveQuery if URL provided
|
|
607
|
+
configure_live_query(opts)
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
# Inspect `opts[:faraday]` for settings that would silently neuter
|
|
611
|
+
# transport security and reject them. Specifically:
|
|
612
|
+
#
|
|
613
|
+
# - `ssl: { verify: false }` on an HTTPS URL — would accept any cert
|
|
614
|
+
# - `proxy: "..."` — would route every request through an attacker-
|
|
615
|
+
# controlled MITM unless explicitly allowlisted
|
|
616
|
+
#
|
|
617
|
+
# @api private
|
|
618
|
+
def validate_faraday_opts!(faraday_opts)
|
|
619
|
+
return unless faraday_opts.is_a?(Hash)
|
|
620
|
+
|
|
621
|
+
ssl = faraday_opts[:ssl] || faraday_opts["ssl"]
|
|
622
|
+
if ssl.is_a?(Hash)
|
|
623
|
+
verify = ssl.key?(:verify) ? ssl[:verify] : ssl["verify"]
|
|
624
|
+
if verify == false && @server_url.to_s.start_with?("https://")
|
|
625
|
+
raise ArgumentError,
|
|
626
|
+
"[Parse::Client] Refusing to disable TLS certificate verification " \
|
|
627
|
+
"(opts[:faraday][:ssl][:verify] = false) on an HTTPS server URL. " \
|
|
628
|
+
"Fix the server certificate or downgrade the URL to http:// " \
|
|
629
|
+
"(with require_https: false) for explicit local testing."
|
|
630
|
+
end
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
proxy = faraday_opts[:proxy] || faraday_opts["proxy"]
|
|
634
|
+
if proxy && !@allow_faraday_proxy
|
|
635
|
+
raise ArgumentError,
|
|
636
|
+
"[Parse::Client] Refusing opts[:faraday][:proxy] = #{proxy.inspect}. " \
|
|
637
|
+
"Routing requests through a proxy can be used to MITM credentials. " \
|
|
638
|
+
"Pass allow_faraday_proxy: true to explicitly opt in."
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
# Suppress Faraday's automatic discovery of HTTPS_PROXY / HTTP_PROXY
|
|
642
|
+
# / NO_PROXY environment variables when the explicit opt-in flag
|
|
643
|
+
# is not set. Without this, a `HTTPS_PROXY` env var leaks every
|
|
644
|
+
# Parse request (and master key) through a process-environment-
|
|
645
|
+
# controlled proxy — a vector that the explicit `proxy:` check
|
|
646
|
+
# above closes but env-discovery silently re-opens. Setting
|
|
647
|
+
# `proxy: nil` is the Faraday-documented way to disable
|
|
648
|
+
# env-proxy autodiscovery.
|
|
649
|
+
faraday_opts[:proxy] = nil unless @allow_faraday_proxy
|
|
650
|
+
end
|
|
651
|
+
private :validate_faraday_opts!
|
|
652
|
+
|
|
653
|
+
# Configure LiveQuery with the given options
|
|
654
|
+
# @param opts [Hash] configuration options
|
|
655
|
+
# @option opts [String] :live_query_url WebSocket URL for LiveQuery server (wss://...)
|
|
656
|
+
# @api private
|
|
657
|
+
def configure_live_query(opts)
|
|
658
|
+
live_query_url = opts[:live_query_url] || ENV["PARSE_LIVE_QUERY_URL"]
|
|
659
|
+
|
|
660
|
+
return unless live_query_url || opts[:live_query]
|
|
661
|
+
|
|
662
|
+
require_relative "live_query"
|
|
663
|
+
|
|
664
|
+
live_query_opts = opts[:live_query].is_a?(Hash) ? opts[:live_query] : {}
|
|
665
|
+
|
|
666
|
+
Parse::LiveQuery.configure(
|
|
667
|
+
url: live_query_url || live_query_opts[:url],
|
|
668
|
+
application_id: @application_id,
|
|
669
|
+
client_key: @api_key,
|
|
670
|
+
master_key: @master_key,
|
|
671
|
+
**live_query_opts,
|
|
672
|
+
)
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
# If set, returns the current retry count for this instance. Otherwise,
|
|
676
|
+
# returns {DEFAULT_RETRIES}. Set to 0 to disable retry mechanism.
|
|
677
|
+
# @return [Integer] the current retry count for this client.
|
|
678
|
+
def retry_limit
|
|
679
|
+
return DEFAULT_RETRIES if @retry_limit.nil?
|
|
680
|
+
@retry_limit
|
|
681
|
+
end
|
|
682
|
+
|
|
683
|
+
# @return [String] the url prefix of the Parse Server url.
|
|
684
|
+
def url_prefix
|
|
685
|
+
@conn.url_prefix
|
|
686
|
+
end
|
|
687
|
+
|
|
688
|
+
# Clear the client cache
|
|
689
|
+
def clear_cache!
|
|
690
|
+
self.cache.clear if self.cache.present?
|
|
691
|
+
end
|
|
692
|
+
|
|
693
|
+
# Send a REST API request to the server. This is the low-level API used for all requests
|
|
694
|
+
# to the Parse server with the provided options. Every request sent to Parse through
|
|
695
|
+
# the client goes through the configured set of middleware that can be modified by applying
|
|
696
|
+
# different headers or specific options.
|
|
697
|
+
# This method supports retrying requests a few times when a {Parse::ServiceUnavailableError}
|
|
698
|
+
# is raised.
|
|
699
|
+
# @param method [Symbol] The method type of the HTTP request (ex. :get, :post).
|
|
700
|
+
# - This parameter can also be a {Parse::Request} object.
|
|
701
|
+
# @param uri [String] the url path. It should not be an absolute url.
|
|
702
|
+
# @param body [Hash] the body of the request.
|
|
703
|
+
# @param query [Hash] the set of url query parameters to use in a GET request.
|
|
704
|
+
# @param headers [Hash] additional headers to apply to this request.
|
|
705
|
+
# @param opts [Hash] a set of options to pass through the middleware stack.
|
|
706
|
+
# - *:cache* [Integer] the number of seconds to cache this specific request.
|
|
707
|
+
# If set to `false`, caching will be disabled completely all together, which means even if
|
|
708
|
+
# a cached response exists, it will not be used.
|
|
709
|
+
# - *:use_master_key* [Boolean] whether this request should send the master key, if
|
|
710
|
+
# it was configured with {Parse.setup}. By default, if a master key was configured,
|
|
711
|
+
# all outgoing requests will contain it in the request header. Default `true`.
|
|
712
|
+
# - *:session_token* [String] The session token to send in this request. This disables
|
|
713
|
+
# sending the master key in the request, and sends this request with the credentials provided by
|
|
714
|
+
# the session_token.
|
|
715
|
+
# - *:retry* [Integer] The number of retrties to perform if the service is unavailable.
|
|
716
|
+
# Set to false to disable the retry mechanism. When performing request retries, the
|
|
717
|
+
# client will sleep for a number of seconds ({Parse::Client::RETRY_DELAY}) between requests.
|
|
718
|
+
# The default value is {Parse::Client::DEFAULT_RETRIES}.
|
|
719
|
+
# @raise Parse::Error::AuthenticationError when HTTP response status is 401 or 403
|
|
720
|
+
# @raise Parse::Error::TimeoutError when HTTP response status is 400 or
|
|
721
|
+
# 408, and the Parse code is 143 or {Parse::Response::ERROR_TIMEOUT}.
|
|
722
|
+
# @raise Parse::Error::ConnectionError when HTTP response status is 404 is not an object not found error.
|
|
723
|
+
# - This will also be raised if after retrying a request a number of times has finally failed.
|
|
724
|
+
# @raise Parse::Error::ProtocolError when HTTP response status is 405 or 406
|
|
725
|
+
# @raise Parse::Error::ServiceUnavailableError when HTTP response status is 500 or 503.
|
|
726
|
+
# - This may also happen when the Parse Server response code is any
|
|
727
|
+
# number less than {Parse::Response::ERROR_SERVICE_UNAVAILABLE}.
|
|
728
|
+
# @raise Parse::Error::ServerError when the Parse response code is less than 100
|
|
729
|
+
# @raise Parse::Error::RequestLimitExceededError when the Parse response code is {Parse::Response::ERROR_EXCEEDED_BURST_LIMIT}.
|
|
730
|
+
# - This usually means you have exceeded the burst limit on requests, which will mean you will be throttled for the
|
|
731
|
+
# next 60 seconds.
|
|
732
|
+
# @raise Parse::Error::InvalidSessionTokenError when the Parse response code is 209.
|
|
733
|
+
# - This means the session token that was sent in the request seems to be invalid.
|
|
734
|
+
# @return [Parse::Response] the response for this request.
|
|
735
|
+
# @see Parse::Middleware::BodyBuilder
|
|
736
|
+
# @see Parse::Middleware::Caching
|
|
737
|
+
# @see Parse::Middleware::Authentication
|
|
738
|
+
# @see Parse::Protocol
|
|
739
|
+
# @see Parse::Request
|
|
740
|
+
def request(method, uri = nil, body: nil, query: nil, headers: nil, opts: {})
|
|
741
|
+
_retry_count ||= self.retry_limit
|
|
742
|
+
|
|
743
|
+
if opts[:retry] == false
|
|
744
|
+
_retry_count = 0
|
|
745
|
+
elsif opts[:retry].to_i > 0
|
|
746
|
+
_retry_count = opts[:retry]
|
|
747
|
+
end
|
|
748
|
+
|
|
749
|
+
headers ||= {}
|
|
750
|
+
# if the first argument is a Parse::Request object, then construct it
|
|
751
|
+
_request = nil
|
|
752
|
+
if method.is_a?(Request)
|
|
753
|
+
_request = method
|
|
754
|
+
method = _request.method
|
|
755
|
+
uri ||= _request.path
|
|
756
|
+
query ||= _request.query
|
|
757
|
+
body ||= _request.body
|
|
758
|
+
headers.merge! _request.headers
|
|
759
|
+
else
|
|
760
|
+
_request = Parse::Request.new(method, uri, body: body, headers: headers, opts: opts)
|
|
761
|
+
end
|
|
762
|
+
|
|
763
|
+
# http method
|
|
764
|
+
method = method.downcase.to_sym
|
|
765
|
+
# set the User-Agent
|
|
766
|
+
headers[USER_AGENT_HEADER] = USER_AGENT_VERSION
|
|
767
|
+
|
|
768
|
+
if opts[:cache] == false
|
|
769
|
+
headers[Parse::Middleware::Caching::CACHE_CONTROL] = "no-cache"
|
|
770
|
+
elsif opts[:cache] == :write_only
|
|
771
|
+
# Write-only mode: skip reading from cache, but still write to cache
|
|
772
|
+
# Useful for fetch!/reload! which want fresh data but should update cache
|
|
773
|
+
headers[Parse::Middleware::Caching::CACHE_WRITE_ONLY] = "true"
|
|
774
|
+
elsif opts[:cache].is_a?(Numeric)
|
|
775
|
+
# specify the cache duration of this request
|
|
776
|
+
headers[Parse::Middleware::Caching::CACHE_EXPIRES_DURATION] = opts[:cache].to_s
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
if opts[:use_master_key] == false
|
|
780
|
+
headers[Parse::Middleware::Authentication::DISABLE_MASTER_KEY] = "true"
|
|
781
|
+
end
|
|
782
|
+
|
|
783
|
+
token = opts[:session_token]
|
|
784
|
+
if token.present?
|
|
785
|
+
token = token.session_token if token.respond_to?(:session_token)
|
|
786
|
+
headers[Parse::Middleware::Authentication::DISABLE_MASTER_KEY] = "true"
|
|
787
|
+
headers[Parse::Protocol::SESSION_TOKEN] = token
|
|
788
|
+
end
|
|
789
|
+
|
|
790
|
+
#if it is a :get request, then use query params, otherwise body.
|
|
791
|
+
params = (method == :get ? query : body) || {}
|
|
792
|
+
# if the path does not start with the '/1/' prefix, then add it to be nice.
|
|
793
|
+
# actually send the request and return the body
|
|
794
|
+
response_env = @conn.send(method, uri, params, headers)
|
|
795
|
+
response = response_env.body
|
|
796
|
+
response.request = _request
|
|
797
|
+
|
|
798
|
+
case response.http_status
|
|
799
|
+
when 401, 403
|
|
800
|
+
Parse::Client._safe_warn("AuthenticationError", response)
|
|
801
|
+
raise Parse::Error::AuthenticationError, response
|
|
802
|
+
when 400, 408
|
|
803
|
+
if response.code == Parse::Response::ERROR_TIMEOUT || response.code == 143 #"net/http: timeout awaiting response headers"
|
|
804
|
+
Parse::Client._safe_warn("TimeoutError", response)
|
|
805
|
+
raise Parse::Error::TimeoutError, response
|
|
806
|
+
end
|
|
807
|
+
when 404
|
|
808
|
+
unless response.object_not_found?
|
|
809
|
+
Parse::Client._safe_warn("ConnectionError", response)
|
|
810
|
+
raise Parse::Error::ConnectionError, response
|
|
811
|
+
end
|
|
812
|
+
when 405, 406
|
|
813
|
+
Parse::Client._safe_warn("ProtocolError", response)
|
|
814
|
+
raise Parse::Error::ProtocolError, response
|
|
815
|
+
when 429 # Request over the throttle limit
|
|
816
|
+
Parse::Client._safe_warn("RequestLimitExceededError", response)
|
|
817
|
+
raise Parse::Error::RequestLimitExceededError, response
|
|
818
|
+
when 500, 503
|
|
819
|
+
Parse::Client._safe_warn("ServiceUnavailableError", response)
|
|
820
|
+
raise Parse::Error::ServiceUnavailableError, response
|
|
821
|
+
end
|
|
822
|
+
|
|
823
|
+
if response.error?
|
|
824
|
+
if response.code <= Parse::Response::ERROR_SERVICE_UNAVAILABLE
|
|
825
|
+
Parse::Client._safe_warn("ServiceUnavailableError", response)
|
|
826
|
+
raise Parse::Error::ServiceUnavailableError, response
|
|
827
|
+
elsif response.code <= 100
|
|
828
|
+
Parse::Client._safe_warn("ServerError", response)
|
|
829
|
+
raise Parse::Error::ServerError, response
|
|
830
|
+
elsif response.code == Parse::Response::ERROR_EXCEEDED_BURST_LIMIT
|
|
831
|
+
Parse::Client._safe_warn("RequestLimitExceededError", response)
|
|
832
|
+
raise Parse::Error::RequestLimitExceededError, response
|
|
833
|
+
elsif response.code == 209 # Error 209: invalid session token
|
|
834
|
+
Parse::Client._safe_warn("InvalidSessionTokenError", response)
|
|
835
|
+
raise Parse::Error::InvalidSessionTokenError, response
|
|
836
|
+
end
|
|
837
|
+
end
|
|
838
|
+
|
|
839
|
+
response
|
|
840
|
+
rescue Parse::Error::RequestLimitExceededError, Parse::Error::ServiceUnavailableError => e
|
|
841
|
+
if _retry_count > 0
|
|
842
|
+
warn "[Parse:Retry] Retries remaining #{_retry_count} : #{response.request}"
|
|
843
|
+
_retry_count -= 1
|
|
844
|
+
# Use Retry-After header if available, otherwise use exponential backoff
|
|
845
|
+
retry_after = response.retry_after if response.respond_to?(:retry_after)
|
|
846
|
+
if retry_after && retry_after > 0
|
|
847
|
+
_retry_delay = retry_after
|
|
848
|
+
warn "[Parse:Retry] Using Retry-After header: #{_retry_delay}s"
|
|
849
|
+
else
|
|
850
|
+
# Deterministic exponential backoff with +/-25% jitter. Never zero —
|
|
851
|
+
# zero-wait retries amplify DoS against upstream and stampede on 429.
|
|
852
|
+
backoff_delay = RETRY_DELAY * (self.retry_limit - _retry_count)
|
|
853
|
+
_retry_delay = backoff_delay * (0.75 + rand * 0.5)
|
|
854
|
+
end
|
|
855
|
+
sleep _retry_delay if _retry_delay > 0
|
|
856
|
+
retry
|
|
857
|
+
end
|
|
858
|
+
raise
|
|
859
|
+
rescue Faraday::ClientError, Net::OpenTimeout => e
|
|
860
|
+
if _retry_count > 0
|
|
861
|
+
warn "[Parse:Retry] Retries remaining #{_retry_count} : #{_request}"
|
|
862
|
+
_retry_count -= 1
|
|
863
|
+
backoff_delay = RETRY_DELAY * (self.retry_limit - _retry_count)
|
|
864
|
+
_retry_delay = backoff_delay * (0.75 + rand * 0.5)
|
|
865
|
+
sleep _retry_delay if _retry_delay > 0
|
|
866
|
+
retry
|
|
867
|
+
end
|
|
868
|
+
raise Parse::Error::ConnectionError, "#{_request} : #{e.class} - #{e.message}"
|
|
869
|
+
end
|
|
870
|
+
|
|
871
|
+
# Send a GET request.
|
|
872
|
+
# @param uri [String] the uri path for this request.
|
|
873
|
+
# @param query [Hash] the set of url query parameters.
|
|
874
|
+
# @param headers [Hash] additional headers to send in this request.
|
|
875
|
+
# @return (see #request)
|
|
876
|
+
def get(uri, query = nil, headers = {})
|
|
877
|
+
request :get, uri, query: query, headers: headers
|
|
878
|
+
end
|
|
879
|
+
|
|
880
|
+
# Send a POST request.
|
|
881
|
+
# @param uri (see #get)
|
|
882
|
+
# @param body [Hash] a hash that will be JSON encoded for the body of this request.
|
|
883
|
+
# @param headers (see #get)
|
|
884
|
+
# @return (see #request)
|
|
885
|
+
def post(uri, body = nil, headers = {})
|
|
886
|
+
request :post, uri, body: body, headers: headers
|
|
887
|
+
end
|
|
888
|
+
|
|
889
|
+
# Send a PUT request.
|
|
890
|
+
# @param uri (see #post)
|
|
891
|
+
# @param body (see #post)
|
|
892
|
+
# @param headers (see #post)
|
|
893
|
+
# @return (see #request)
|
|
894
|
+
def put(uri, body = nil, headers = {})
|
|
895
|
+
request :put, uri, body: body, headers: headers
|
|
896
|
+
end
|
|
897
|
+
|
|
898
|
+
# Send a DELETE request.
|
|
899
|
+
# @param uri (see #post)
|
|
900
|
+
# @param body (see #post)
|
|
901
|
+
# @param headers (see #post)
|
|
902
|
+
# @return (see #request)
|
|
903
|
+
def delete(uri, body = nil, headers = {})
|
|
904
|
+
request :delete, uri, body: body, headers: headers
|
|
905
|
+
end
|
|
906
|
+
|
|
907
|
+
# Send a {Parse::Request} object.
|
|
908
|
+
# @param req [Parse::Request] the request to send
|
|
909
|
+
# @raise ArgumentError if req is not of type Parse::Request.
|
|
910
|
+
# @return (see #request)
|
|
911
|
+
def send_request(req) #Parse::Request object
|
|
912
|
+
raise ArgumentError, "Object not of Parse::Request type." unless req.is_a?(Parse::Request)
|
|
913
|
+
request req.method, req.path, req.body, req.headers
|
|
914
|
+
end
|
|
915
|
+
|
|
916
|
+
# The connectable module adds methods to objects so that they can get a default
|
|
917
|
+
# Parse::Client object if needed. This is mainly used for Parse::Query and Parse::Object classes.
|
|
918
|
+
# This is included in the Parse::Model class.
|
|
919
|
+
# Any subclass can override their `client` methods to provide a different session to use
|
|
920
|
+
module Connectable
|
|
921
|
+
|
|
922
|
+
# @!visibility private
|
|
923
|
+
def self.included(baseClass)
|
|
924
|
+
baseClass.extend ClassMethods
|
|
925
|
+
end
|
|
926
|
+
# Class methods to be added to any object that wants to have standard access to
|
|
927
|
+
# a the default {Parse::Client} instance.
|
|
928
|
+
module ClassMethods
|
|
929
|
+
|
|
930
|
+
# @return [Parse::Client] the current client for :default.
|
|
931
|
+
attr_writer :client
|
|
932
|
+
|
|
933
|
+
def client
|
|
934
|
+
@client ||= Parse::Client.client #defaults to :default tag
|
|
935
|
+
end
|
|
936
|
+
end
|
|
937
|
+
|
|
938
|
+
# @return [Parse::Client] the current client defined for the class.
|
|
939
|
+
def client
|
|
940
|
+
self.class.client
|
|
941
|
+
end
|
|
942
|
+
end #Connectable
|
|
943
|
+
end
|
|
944
|
+
|
|
945
|
+
# Helper method that users should call to setup the client stack.
|
|
946
|
+
# A block can be passed in order to do additional client configuration.
|
|
947
|
+
# To connect to a Parse server, you will need a minimum of an application_id,
|
|
948
|
+
# an api_key and a server_url. To connect to the server endpoint, you use the
|
|
949
|
+
# {Parse.setup} method below.
|
|
950
|
+
#
|
|
951
|
+
# @example (see Parse::Client.setup)
|
|
952
|
+
# @param opts (see Parse::Client.setup)
|
|
953
|
+
# @option opts (see Parse::Client.setup)
|
|
954
|
+
# @yield (see Parse::Client.setup)
|
|
955
|
+
# @return (see Parse::Client.setup)
|
|
956
|
+
# @see Parse::Client.setup
|
|
957
|
+
def self.setup(opts = {}, &block)
|
|
958
|
+
if block_given?
|
|
959
|
+
Parse::Client.new(opts, &block)
|
|
960
|
+
else
|
|
961
|
+
Parse::Client.new(opts)
|
|
962
|
+
end
|
|
963
|
+
end
|
|
964
|
+
|
|
965
|
+
# @!visibility private
|
|
966
|
+
# Unwrap the `{ "result" => ... }` envelope from a successful cloud-code response.
|
|
967
|
+
# Guards against unusual server payloads (non-Hash bodies) by returning the raw
|
|
968
|
+
# result rather than raising TypeError on `String#[]`/`Integer#[]`.
|
|
969
|
+
def self._extract_cloud_result(response)
|
|
970
|
+
r = response.result
|
|
971
|
+
r.is_a?(Hash) ? r["result"] : r
|
|
972
|
+
end
|
|
973
|
+
|
|
974
|
+
# Helper method to trigger cloud jobs and get results.
|
|
975
|
+
# @param name [String] the name of the cloud code job to trigger.
|
|
976
|
+
# @param body [Hash] the set of parameters to pass to the job.
|
|
977
|
+
# @param opts (see Parse.call_function)
|
|
978
|
+
# @return (see Parse.call_function)
|
|
979
|
+
def self.trigger_job(name, body = {}, **opts)
|
|
980
|
+
conn = opts[:session] || opts[:client] || :default
|
|
981
|
+
|
|
982
|
+
# Extract request options for the API call
|
|
983
|
+
request_opts = {}
|
|
984
|
+
request_opts[:session_token] = opts[:session_token] if opts[:session_token]
|
|
985
|
+
request_opts[:master_key] = opts[:master_key] if opts[:master_key]
|
|
986
|
+
|
|
987
|
+
response = Parse::Client.client(conn).trigger_job(name, body, opts: request_opts)
|
|
988
|
+
return response if opts[:raw].present?
|
|
989
|
+
if response.error?
|
|
990
|
+
Parse::Client._safe_warn("CloudCodeError", response, name: name)
|
|
991
|
+
return nil
|
|
992
|
+
end
|
|
993
|
+
_extract_cloud_result(response)
|
|
994
|
+
end
|
|
995
|
+
|
|
996
|
+
# Same as {Parse.trigger_job} but raises {Parse::Error::CloudCodeError} when
|
|
997
|
+
# the job returns an error instead of silently returning nil. HTTP-level
|
|
998
|
+
# errors (auth, timeouts, throttling, etc.) still raise their specific
|
|
999
|
+
# {Parse::Error} subclasses as the underlying client does.
|
|
1000
|
+
# @param name (see Parse.trigger_job)
|
|
1001
|
+
# @param body (see Parse.trigger_job)
|
|
1002
|
+
# @param opts (see Parse.trigger_job) — :raw is ignored.
|
|
1003
|
+
# @raise [Parse::Error::CloudCodeError] when the response indicates a cloud-code error.
|
|
1004
|
+
# @return [Object] the result data of the response.
|
|
1005
|
+
def self.trigger_job!(name, body = {}, **opts)
|
|
1006
|
+
response = trigger_job(name, body, **opts.merge(raw: true))
|
|
1007
|
+
raise Parse::Error::CloudCodeError.new(name, response) if response.error?
|
|
1008
|
+
_extract_cloud_result(response)
|
|
1009
|
+
end
|
|
1010
|
+
|
|
1011
|
+
# Helper method to trigger cloud jobs with a session token.
|
|
1012
|
+
# This is a convenience method that ensures proper session token handling.
|
|
1013
|
+
# @param name [String] the name of the cloud code job to trigger.
|
|
1014
|
+
# @param body [Hash] the set of parameters to pass to the job.
|
|
1015
|
+
# @param session_token [String] the session token for authenticated requests.
|
|
1016
|
+
# @param opts [Hash] additional options (same as trigger_job).
|
|
1017
|
+
# @return [Object] the result data of the response. nil if there was an error.
|
|
1018
|
+
def self.trigger_job_with_session(name, body = {}, session_token, **opts)
|
|
1019
|
+
opts[:session_token] = session_token
|
|
1020
|
+
trigger_job(name, body, **opts)
|
|
1021
|
+
end
|
|
1022
|
+
|
|
1023
|
+
# Same as {Parse.trigger_job_with_session} but raises
|
|
1024
|
+
# {Parse::Error::CloudCodeError} when the job returns an error instead of
|
|
1025
|
+
# silently returning nil.
|
|
1026
|
+
# @param name (see Parse.trigger_job_with_session)
|
|
1027
|
+
# @param body (see Parse.trigger_job_with_session)
|
|
1028
|
+
# @param session_token (see Parse.trigger_job_with_session)
|
|
1029
|
+
# @param opts (see Parse.trigger_job_with_session)
|
|
1030
|
+
# @raise [Parse::Error::CloudCodeError] when the response indicates a cloud-code error.
|
|
1031
|
+
# @return [Object] the result data of the response.
|
|
1032
|
+
def self.trigger_job_with_session!(name, body = {}, session_token, **opts)
|
|
1033
|
+
opts[:session_token] = session_token
|
|
1034
|
+
trigger_job!(name, body, **opts)
|
|
1035
|
+
end
|
|
1036
|
+
|
|
1037
|
+
# Helper method to call cloud functions and get results.
|
|
1038
|
+
# @param name [String] the name of the cloud code function to call.
|
|
1039
|
+
# @param body [Hash] the set of parameters to pass to the function.
|
|
1040
|
+
# @param opts [Hash] additional options.
|
|
1041
|
+
# @option opts [String] :session_token The session token for authenticated requests.
|
|
1042
|
+
# @option opts [Symbol] :session The client connection to use (alternative to :client).
|
|
1043
|
+
# @option opts [Symbol] :client The client connection to use.
|
|
1044
|
+
# @option opts [Boolean] :raw Whether to return the raw response object.
|
|
1045
|
+
# @option opts [Boolean] :master_key Whether to use the master key for this request.
|
|
1046
|
+
# @return [Object] the result data of the response. nil if there was an error.
|
|
1047
|
+
def self.call_function(name, body = {}, **opts)
|
|
1048
|
+
conn = opts[:session] || opts[:client] || :default
|
|
1049
|
+
|
|
1050
|
+
# Extract request options for the API call
|
|
1051
|
+
request_opts = {}
|
|
1052
|
+
request_opts[:session_token] = opts[:session_token] if opts[:session_token]
|
|
1053
|
+
request_opts[:master_key] = opts[:master_key] if opts[:master_key]
|
|
1054
|
+
|
|
1055
|
+
response = Parse::Client.client(conn).call_function(name, body, opts: request_opts)
|
|
1056
|
+
return response if opts[:raw].present?
|
|
1057
|
+
if response.error?
|
|
1058
|
+
Parse::Client._safe_warn("CloudCodeError", response, name: name)
|
|
1059
|
+
return nil
|
|
1060
|
+
end
|
|
1061
|
+
_extract_cloud_result(response)
|
|
1062
|
+
end
|
|
1063
|
+
|
|
1064
|
+
# Same as {Parse.call_function} but raises {Parse::Error::CloudCodeError}
|
|
1065
|
+
# when the cloud function returns an error instead of silently returning nil.
|
|
1066
|
+
# HTTP-level errors (auth, timeouts, throttling, etc.) still raise their
|
|
1067
|
+
# specific {Parse::Error} subclasses as the underlying client does.
|
|
1068
|
+
# @param name (see Parse.call_function)
|
|
1069
|
+
# @param body (see Parse.call_function)
|
|
1070
|
+
# @param opts (see Parse.call_function) — :raw is ignored.
|
|
1071
|
+
# @raise [Parse::Error::CloudCodeError] when the response indicates a cloud-code error.
|
|
1072
|
+
# @return [Object] the result data of the response.
|
|
1073
|
+
def self.call_function!(name, body = {}, **opts)
|
|
1074
|
+
response = call_function(name, body, **opts.merge(raw: true))
|
|
1075
|
+
raise Parse::Error::CloudCodeError.new(name, response) if response.error?
|
|
1076
|
+
_extract_cloud_result(response)
|
|
1077
|
+
end
|
|
1078
|
+
|
|
1079
|
+
# Helper method to call cloud functions with a session token.
|
|
1080
|
+
# This is a convenience method that ensures proper session token handling.
|
|
1081
|
+
# @param name [String] the name of the cloud code function to call.
|
|
1082
|
+
# @param body [Hash] the set of parameters to pass to the function.
|
|
1083
|
+
# @param session_token [String] the session token for authenticated requests.
|
|
1084
|
+
# @param opts [Hash] additional options (same as call_function).
|
|
1085
|
+
# @return [Object] the result data of the response. nil if there was an error.
|
|
1086
|
+
def self.call_function_with_session(name, body = {}, session_token, **opts)
|
|
1087
|
+
opts[:session_token] = session_token
|
|
1088
|
+
call_function(name, body, **opts)
|
|
1089
|
+
end
|
|
1090
|
+
|
|
1091
|
+
# Same as {Parse.call_function_with_session} but raises
|
|
1092
|
+
# {Parse::Error::CloudCodeError} when the cloud function returns an error
|
|
1093
|
+
# instead of silently returning nil.
|
|
1094
|
+
# @param name (see Parse.call_function_with_session)
|
|
1095
|
+
# @param body (see Parse.call_function_with_session)
|
|
1096
|
+
# @param session_token (see Parse.call_function_with_session)
|
|
1097
|
+
# @param opts (see Parse.call_function_with_session)
|
|
1098
|
+
# @raise [Parse::Error::CloudCodeError] when the response indicates a cloud-code error.
|
|
1099
|
+
# @return [Object] the result data of the response.
|
|
1100
|
+
def self.call_function_with_session!(name, body = {}, session_token, **opts)
|
|
1101
|
+
opts[:session_token] = session_token
|
|
1102
|
+
call_function!(name, body, **opts)
|
|
1103
|
+
end
|
|
1104
|
+
end
|