shipeasy-sdk 3.0.0 → 3.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a34364fd54857fe0b04997063d9d2bbb6296e281a3db8b7389efd84c5f1b4a1
4
- data.tar.gz: bb1c68285633d2f1a5709286197b9a0aacaf7c78d950908e414f3fde64f44144
3
+ metadata.gz: 49743db7faf0600dcc8a7cacb3f7a6fc7418c4d3d125efa6fdd1067b24fe4d16
4
+ data.tar.gz: 031b3acc69308ce512fb67c073b1f7f1894e276e17ab23f5ff3495678c2dab99
5
5
  SHA512:
6
- metadata.gz: f4f9a1149dabefce47743afb04bec2e3f3e6dceac88c9e1a48a2cdb187b87bce0c5c9853cc68afe3f64f7a521fe8dcb4d5017c679ea362f19f8b598d71b3dbcb
7
- data.tar.gz: b82a76824d4d0a90cc481453c5036a53cdb1adb30e74630ac843c6d6582fa294391e440b0d29b044eebf25071eb264bbab3a74491fac3939a815064ca1fd0f92
6
+ metadata.gz: 479ca3e747f954922315f6bb062b385ba8e7710ac37799d7a732f8da63de87321d5c497b8ee3cc605cb80c66bd9f4ebbceea6acc9486ff7d37d8f0b9c153f628
7
+ data.tar.gz: 1f9a70f2ccff8c6fcaeb5a9e913657fb88ceac7c37505f21d409880740d1d7af4b952732a3607ee94908a1e8c1921d23e9997852c3622e8a315ab1168a2c2def
@@ -25,7 +25,18 @@ module Shipeasy
25
25
  # Advanced `configure` options — threaded into the global Engine `configure`
26
26
  # builds, so callers never construct an Engine themselves:
27
27
  # - env (default "prod"): deployment tag on see() events + usage telemetry.
28
- # - disable_telemetry (default false): opt out of per-eval usage telemetry.
28
+ # - is_network_enabled (default: environment-derived): master switch for ALL
29
+ # outbound requests (flag/experiment/config fetch, track, exposure logging,
30
+ # see() reports, AND telemetry). Defaults ON in production and OFF in every
31
+ # other environment, so a dev machine / CI run stays fully offline unless
32
+ # you opt in. Explicit true/false always overrides. "Production" is decided
33
+ # from SHIPEASY_ENV / RAILS_ENV / RACK_ENV / APP_ENV, then the `env` tag
34
+ # (see sdk/env.rb). When off, reads answer from overrides / in-code
35
+ # defaults and nothing is sent.
36
+ # - disable_telemetry (default: environment-derived — off in prod, i.e.
37
+ # telemetry ON in prod / OFF outside prod): opt out of per-eval usage
38
+ # telemetry. Explicit true/false overrides; forced off when the network is
39
+ # disabled.
29
40
  # - telemetry_url: override the telemetry endpoint (rarely needed).
30
41
  # - private_attributes: attribute keys stripped from every outbound event
31
42
  # before it leaves the process (they still drive targeting locally).
@@ -35,7 +46,7 @@ module Shipeasy
35
46
  # swallows one of its OWN internal errors it normally ships a structured
36
47
  # see event to Shipeasy's own project (NOT yours) so the SDK team can
37
48
  # track SDK bugs; set true to disable that entirely.
38
- attr_accessor :env, :disable_telemetry, :telemetry_url,
49
+ attr_accessor :env, :is_network_enabled, :disable_telemetry, :telemetry_url,
39
50
  :private_attributes, :sticky_store,
40
51
  :disable_internal_error_reporting
41
52
 
@@ -80,7 +91,10 @@ module Shipeasy
80
91
  @init = true
81
92
  @poll = false
82
93
  @env = "prod"
83
- @disable_telemetry = false
94
+ # nil ⇒ environment-derived default (see Engine / sdk/env.rb). An explicit
95
+ # true/false set in the configure block always overrides.
96
+ @is_network_enabled = nil
97
+ @disable_telemetry = nil
84
98
  @disable_internal_error_reporting = false
85
99
  @telemetry_url = nil
86
100
  @private_attributes = nil
@@ -159,6 +173,7 @@ module Shipeasy
159
173
  api_key: cfg.api_key,
160
174
  base_url: cfg.base_url,
161
175
  env: cfg.env,
176
+ is_network_enabled: cfg.is_network_enabled,
162
177
  disable_telemetry: cfg.disable_telemetry,
163
178
  telemetry_url: cfg.telemetry_url,
164
179
  private_attributes: cfg.private_attributes,
@@ -170,13 +185,13 @@ module Shipeasy
170
185
  # Capture +engine+ in the closure (not the @engine ivar, which a concurrent
171
186
  # reset/reconfigure could nil out before the thread runs).
172
187
  if cfg.poll
173
- Thread.new do
188
+ @boot_thread = Thread.new do
174
189
  engine.init # initial fetch + background poll thread
175
190
  rescue => e
176
191
  Shipeasy::Logging.error "[shipeasy] configure(poll) background poll failed: #{e.message}"
177
192
  end
178
193
  elsif cfg.init
179
- Thread.new do
194
+ @boot_thread = Thread.new do
180
195
  engine.init_once
181
196
  rescue => e
182
197
  Shipeasy::Logging.error "[shipeasy] configure() one-shot fetch failed: #{e.message}"
@@ -327,6 +342,13 @@ module Shipeasy
327
342
 
328
343
  # Reset the config back to defaults — primarily for tests.
329
344
  def reset_config!
345
+ # Reap the fire-and-forget boot thread first: it holds its own engine
346
+ # reference, so left alive it can call init/init_once during the NEXT
347
+ # test example and trip any_instance expectations armed there.
348
+ if (t = @boot_thread)
349
+ t.join(1) || t.kill
350
+ @boot_thread = nil
351
+ end
330
352
  @config = nil
331
353
  @flags_pid = nil
332
354
  @flags&.destroy
@@ -4,6 +4,7 @@ require "json"
4
4
  require "thread"
5
5
  require "cgi"
6
6
  require_relative "logging"
7
+ require_relative "sdk/env"
7
8
  require_relative "sdk/eval"
8
9
  require_relative "sdk/telemetry"
9
10
  require_relative "sdk/anon_id"
@@ -35,7 +36,7 @@ module Shipeasy
35
36
  # /sdk/i18n/loader.js) — distinct from the edge API the blobs are fetched from.
36
37
  DEFAULT_CDN_BASE = "https://cdn.shipeasy.ai"
37
38
 
38
- def initialize(api_key:, base_url: nil, env: "prod", disable_telemetry: false, telemetry_url: nil, test_mode: false, private_attributes: nil, sticky_store: nil, log_level: nil, disable_internal_error_reporting: false)
39
+ def initialize(api_key:, base_url: nil, env: "prod", is_network_enabled: nil, disable_telemetry: nil, telemetry_url: nil, test_mode: false, private_attributes: nil, sticky_store: nil, log_level: nil, disable_internal_error_reporting: false)
39
40
  # SDK-wide diagnostic verbosity. Set the leveled logger from the passed
40
41
  # level (default :warn; unknown falls back to :warn). The logger is
41
42
  # module-scoped, so the last-built engine wins — mirrors the TS SDK,
@@ -59,14 +60,30 @@ module Shipeasy
59
60
  # evaluation answers come purely from local overrides. Built via the
60
61
  # Engine.for_testing factory; see clear_overrides / override_*.
61
62
  @test_mode = test_mode
62
- # Per-evaluation usage telemetry. ON by default; pass
63
- # disable_telemetry: true to opt out. See telemetry.rb.
63
+ # Environment-derived egress default. Both the master network switch and
64
+ # usage telemetry default ON in production and OFF everywhere else, so an
65
+ # app that embeds the SDK is quiet by default on a dev machine or in CI.
66
+ # The production decision consults native env vars first (SHIPEASY_ENV /
67
+ # RAILS_ENV / RACK_ENV / APP_ENV), then falls back to the configured `env`
68
+ # tag. See sdk/env.rb.
69
+ prod = Shipeasy::SDK::Env.is_production_env(env)
70
+ # Master network gate. test_mode always forces the SDK fully offline;
71
+ # otherwise honour an explicit is_network_enabled, else default to
72
+ # prod-on. When @offline, every fetch / track / exposure / see() / poll /
73
+ # telemetry send is a no-op — reads answer from overrides / in-code
74
+ # defaults only.
75
+ network_enabled = @test_mode ? false : (is_network_enabled.nil? ? prod : (is_network_enabled ? true : false))
76
+ @offline = !network_enabled
77
+ # Per-evaluation usage telemetry. Honour an explicit disable_telemetry,
78
+ # else default to prod-on (off outside production). Forced off whenever
79
+ # the master network switch is off. See telemetry.rb.
80
+ telemetry_disabled = @offline || (disable_telemetry.nil? ? !prod : (disable_telemetry ? true : false))
64
81
  @telemetry = Telemetry.new(
65
82
  endpoint: telemetry_url || Telemetry::DEFAULT_TELEMETRY_URL,
66
83
  sdk_key: api_key,
67
84
  side: "server",
68
85
  env: env,
69
- disabled: disable_telemetry,
86
+ disabled: telemetry_disabled,
70
87
  )
71
88
  @flags_blob = nil
72
89
  @exps_blob = nil
@@ -103,7 +120,7 @@ module Shipeasy
103
120
  Shipeasy::SDK::InternalReport.set_context(
104
121
  side: "server",
105
122
  sdk_version: Shipeasy::SDK::VERSION,
106
- enabled: !test_mode && !disable_internal_error_reporting,
123
+ enabled: !@offline && !disable_internal_error_reporting,
107
124
  )
108
125
  # Register as the default client backing the module-level Shipeasy::SDK
109
126
  # .see/.see_violation funcs (last constructed wins — the server-SDK
@@ -153,14 +170,14 @@ module Shipeasy
153
170
  end
154
171
 
155
172
  def init
156
- return if @test_mode
173
+ return if @offline
157
174
  fetch_all
158
175
  @initialized = true
159
176
  start_poll
160
177
  end
161
178
 
162
179
  def init_once
163
- return if @test_mode
180
+ return if @offline
164
181
  return if @initialized
165
182
  fetch_all
166
183
  @initialized = true
@@ -470,7 +487,7 @@ module Shipeasy
470
487
 
471
488
  def track(user_id, event_name, props = {})
472
489
  safe_run("track('#{event_name}')", nil) do
473
- next if @test_mode
490
+ next if @offline
474
491
 
475
492
  safe_props = strip_private(props)
476
493
 
@@ -583,7 +600,7 @@ module Shipeasy
583
600
  # spam /collect. Fire-and-forget; no-op in test mode. This is how
584
601
  # assign_universe auto-logs — the browser's auto-exposure parity for SSR.
585
602
  def post_exposure(user, experiment, group)
586
- return if @test_mode
603
+ return if @offline
587
604
  u = user.transform_keys(&:to_s)
588
605
  uid = u["user_id"] || u["anonymous_id"]
589
606
  dedup_key = "#{uid}:#{experiment}:#{group}"
@@ -614,7 +631,7 @@ module Shipeasy
614
631
  # Build the wire event and fire-and-forget POST it to /collect. No-op in
615
632
  # test mode (mirrors track). Spam-guarded. Never raises into caller code.
616
633
  def dispatch_see(built)
617
- return if @test_mode
634
+ return if @offline
618
635
 
619
636
  ev = See.build_event(
620
637
  built.problem,
@@ -0,0 +1,55 @@
1
+ module Shipeasy
2
+ module SDK
3
+ # Native runtime-environment detection.
4
+ #
5
+ # Used ONLY to pick the DEFAULT for outbound egress when the caller does not
6
+ # set it explicitly:
7
+ # - is the SDK allowed to make network requests at all (is_network_enabled)?
8
+ # - is per-evaluation usage telemetry allowed (disable_telemetry)?
9
+ #
10
+ # Both default to ON in production and OFF everywhere else, so a local/dev/CI
11
+ # run of an app that embeds the SDK never phones home unless it explicitly
12
+ # opts in.
13
+ #
14
+ # Precedence for the production decision (mirrors the TS SDK's src/env.ts):
15
+ # 1. A native runtime env var — SHIPEASY_ENV, then RAILS_ENV, then RACK_ENV,
16
+ # then APP_ENV. A value of "production"/"prod" (case-insensitive) ⇒ prod;
17
+ # anything else present ("development"/"staging"/"test"/…) ⇒ not prod.
18
+ # 2. When no native env var is set (e.g. serverless / some containers), fall
19
+ # back to the SDK's OWN configured `env` option, which the caller sets and
20
+ # which itself defaults to "prod". This keeps a real production deploy
21
+ # "on" by default while an `env: "dev"` config stays quiet.
22
+ #
23
+ # The env option is always present (it defaults to "prod"), so the production
24
+ # decision is always inferrable — the SDK never has to make the field required.
25
+ module Env
26
+ # Native env vars consulted, in precedence order.
27
+ NATIVE_ENV_VARS = %w[SHIPEASY_ENV RAILS_ENV RACK_ENV APP_ENV].freeze
28
+
29
+ module_function
30
+
31
+ # True when the host runtime looks like a production deployment.
32
+ # +configured_env+ is the SDK's own `env` option (dev/staging/prod); it is
33
+ # consulted ONLY when no native runtime env var is set.
34
+ def is_production_env(configured_env = nil)
35
+ native = read_native_env
36
+ return native == "production" || native == "prod" unless native.nil?
37
+
38
+ (configured_env || "prod").to_s.strip.downcase == "prod"
39
+ end
40
+
41
+ # Read the first present native env var (lowercased, trimmed), or nil when
42
+ # none of them is set to a non-empty value.
43
+ def read_native_env
44
+ NATIVE_ENV_VARS.each do |name|
45
+ raw = ENV[name]
46
+ next if raw.nil?
47
+
48
+ v = raw.strip.downcase
49
+ return v unless v.empty?
50
+ end
51
+ nil
52
+ end
53
+ end
54
+ end
55
+ end
@@ -1,5 +1,5 @@
1
1
  module Shipeasy
2
2
  module SDK
3
- VERSION = "3.0.0"
3
+ VERSION = "3.1.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shipeasy-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shipeasy, Inc.
@@ -142,6 +142,7 @@ files:
142
142
  - lib/shipeasy/i18n/view_helpers.rb
143
143
  - lib/shipeasy/logging.rb
144
144
  - lib/shipeasy/sdk/anon_id.rb
145
+ - lib/shipeasy/sdk/env.rb
145
146
  - lib/shipeasy/sdk/eval.rb
146
147
  - lib/shipeasy/sdk/internal_report.rb
147
148
  - lib/shipeasy/sdk/murmur3.rb