pushizo 0.1.0 → 0.2.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.
@@ -1,40 +1,40 @@
1
- # frozen_string_literal: true
2
-
3
- require "base64"
4
- require "json"
5
- require "openssl"
6
-
7
- module Pushizo
8
- # SDK JWT identity verification belirteci (ES256; ADR-0023 §9): sub = external_id, aud = app_id, ömür en çok
9
- # 24 saat. Uygulamanız belirteci kendi sunucusundan alır ve istemci SDK'sının login(external_id, jwt) çağrısına verir.
10
- module IdentityToken
11
- MAX_LIFETIME_SECONDS = 86_400
12
-
13
- module_function
14
-
15
- def create(private_key_pem, kid:, app_id:, external_id:, expires_in: 3600, now: nil)
16
- raise ArgumentError, "expires_in 1…86400 aralığında olmalı" unless expires_in.is_a?(Integer) && expires_in.between?(1, MAX_LIFETIME_SECONDS)
17
-
18
- key = OpenSSL::PKey.read(private_key_pem)
19
- raise ArgumentError, "ES256 için P-256 EC özel anahtarı gerekli" unless key.is_a?(OpenSSL::PKey::EC) && key.group.curve_name == "prime256v1"
20
-
21
- iat = (now || Time.now).to_i
22
- header = b64url(JSON.generate({ alg: "ES256", typ: "JWT", kid: kid }))
23
- payload = b64url(JSON.generate({ sub: external_id, aud: app_id, iat: iat, exp: iat + expires_in }))
24
- signing_input = "#{header}.#{payload}"
25
- der = key.sign(OpenSSL::Digest.new("SHA256"), signing_input)
26
- # JWS ES256 imzası DER değil r||s (her biri 32 bayt).
27
- r, s = OpenSSL::ASN1.decode(der).value.map { |i| i.value.to_s(2).rjust(32, "\0".b) }
28
- "#{signing_input}.#{b64url(r + s)}"
29
- end
30
-
31
- def b64url(data)
32
- Base64.urlsafe_encode64(data, padding: false)
33
- end
34
- end
35
-
36
- # Kısayol: Pushizo.identity_token(pem, kid:, app_id:, external_id:).
37
- def self.identity_token(private_key_pem, **kwargs)
38
- IdentityToken.create(private_key_pem, **kwargs)
39
- end
40
- end
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "openssl"
5
+
6
+ module Pushizo
7
+ # SDK JWT identity verification belirteci (ES256; ADR-0023 §9): sub = external_id, aud = app_id, ömür en çok
8
+ # 24 saat. Uygulamanız belirteci kendi sunucusundan alır ve istemci SDK'sının login(external_id, jwt) çağrısına verir.
9
+ module IdentityToken
10
+ MAX_LIFETIME_SECONDS = 86_400
11
+
12
+ module_function
13
+
14
+ def create(private_key_pem, kid:, app_id:, external_id:, expires_in: 3600, now: nil)
15
+ raise ArgumentError, "expires_in 1…86400 aralığında olmalı" unless expires_in.is_a?(Integer) && expires_in.between?(1, MAX_LIFETIME_SECONDS)
16
+
17
+ key = OpenSSL::PKey.read(private_key_pem)
18
+ raise ArgumentError, "ES256 için P-256 EC özel anahtarı gerekli" unless key.is_a?(OpenSSL::PKey::EC) && key.group.curve_name == "prime256v1"
19
+
20
+ iat = (now || Time.now).to_i
21
+ header = b64url(JSON.generate({ alg: "ES256", typ: "JWT", kid: kid }))
22
+ payload = b64url(JSON.generate({ sub: external_id, aud: app_id, iat: iat, exp: iat + expires_in }))
23
+ signing_input = "#{header}.#{payload}"
24
+ der = key.sign(OpenSSL::Digest.new("SHA256"), signing_input)
25
+ # JWS ES256 imzası DER değil r||s (her biri 32 bayt).
26
+ r, s = OpenSSL::ASN1.decode(der).value.map { |i| i.value.to_s(2).rjust(32, "\0".b) }
27
+ "#{signing_input}.#{b64url(r + s)}"
28
+ end
29
+
30
+ def b64url(data)
31
+ # base64 gem'i Ruby 3.4'ten beri varsayılan değil; bağımlılık eklememek için çekirdek pack kullanılır.
32
+ [data].pack("m0").tr("+/", "-_").delete("=")
33
+ end
34
+ end
35
+
36
+ # Kısayol: Pushizo.identity_token(pem, kid:, app_id:, external_id:).
37
+ def self.identity_token(private_key_pem, **kwargs)
38
+ IdentityToken.create(private_key_pem, **kwargs)
39
+ end
40
+ end
data/lib/pushizo/model.rb CHANGED
@@ -1,117 +1,117 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
-
5
- module Pushizo
6
- # Çalışma zamanı tip dönüşümü: API yanıtlarını model sınıflarına, istek gövdelerini JSON uyumlu hash'e çevirir.
7
- # Tip tanımı: :string, :integer, :number, :boolean, :binary, :any, [:array, T], [:map, T] veya model sınıf adı (String).
8
- module Types
9
- module_function
10
-
11
- def coerce(value, type)
12
- return value if value.nil?
13
-
14
- case type
15
- when String
16
- klass = Pushizo::Models.const_get(type)
17
- value.is_a?(Hash) ? klass.new(value) : value
18
- when Array
19
- kind, inner = type
20
- if kind == :array && value.is_a?(Array)
21
- value.map { |v| coerce(v, inner) }
22
- elsif kind == :map && value.is_a?(Hash)
23
- value.each_with_object({}) { |(k, v), h| h[k.to_s] = coerce(v, inner) }
24
- else
25
- value
26
- end
27
- else
28
- value
29
- end
30
- end
31
-
32
- # Model, sembol anahtarlı hash ve iç içe yapıları JSON uyumlu (string anahtarlı) değere çevirir.
33
- def serialize(value)
34
- case value
35
- when BaseModel then serialize(value.to_h)
36
- when Hash then value.each_with_object({}) { |(k, v), h| h[k.to_s] = serialize(v) }
37
- when Array then value.map { |v| serialize(v) }
38
- when Symbol then value.to_s
39
- else value
40
- end
41
- end
42
- end
43
-
44
- # Hash tabanlı model: alanlara snake_case okuyucu/yazıcı ve [] ile erişilir; bilinmeyen alanlar korunur.
45
- #
46
- # msg = hs.notifications.get("msg_1")
47
- # msg.status # => "completed"
48
- # msg["status"] # => "completed"
49
- # msg.to_h # => { "id" => …, "status" => … }
50
- class BaseModel
51
- class << self
52
- # JSON adı => [metot adı, tip]
53
- def fields
54
- @fields ||= {}
55
- end
56
-
57
- def field(json_name, method_name, type)
58
- fields[json_name] = [method_name, type]
59
- define_method(method_name) { Pushizo::Types.coerce(@attributes[json_name], type) }
60
- define_method(:"#{method_name}=") { |v| @attributes[json_name] = Pushizo::Types.serialize(v) }
61
- end
62
-
63
- # JSON adına veya metot adına göre JSON anahtarı.
64
- def json_key(key)
65
- name = key.to_s
66
- return name if fields.key?(name)
67
-
68
- found = fields.find { |_json, (method, _type)| method.to_s == name }
69
- found ? found[0] : name
70
- end
71
- end
72
-
73
- def initialize(attributes = {})
74
- @attributes = {}
75
- (attributes || {}).each { |k, v| @attributes[self.class.json_key(k)] = Pushizo::Types.serialize(v) }
76
- end
77
-
78
- # JSON veya metot adıyla okuma (model alanları model nesnesine çevrilir).
79
- def [](key)
80
- json = self.class.json_key(key)
81
- entry = self.class.fields[json]
82
- entry ? Pushizo::Types.coerce(@attributes[json], entry[1]) : @attributes[json]
83
- end
84
-
85
- def []=(key, value)
86
- @attributes[self.class.json_key(key)] = Pushizo::Types.serialize(value)
87
- end
88
-
89
- def key?(key)
90
- @attributes.key?(self.class.json_key(key))
91
- end
92
-
93
- # JSON adlarıyla ham hash (derin kopya).
94
- def to_h
95
- Marshal.load(Marshal.dump(@attributes))
96
- end
97
- alias to_hash to_h
98
-
99
- def to_json(*args)
100
- @attributes.to_json(*args)
101
- end
102
-
103
- def ==(other)
104
- other.class == self.class && other.to_h == to_h
105
- end
106
- alias eql? ==
107
-
108
- def hash
109
- [self.class, @attributes].hash
110
- end
111
-
112
- def inspect
113
- "#<#{self.class.name} #{@attributes.inspect}>"
114
- end
115
- alias to_s inspect
116
- end
117
- end
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Pushizo
6
+ # Çalışma zamanı tip dönüşümü: API yanıtlarını model sınıflarına, istek gövdelerini JSON uyumlu hash'e çevirir.
7
+ # Tip tanımı: :string, :integer, :number, :boolean, :binary, :any, [:array, T], [:map, T] veya model sınıf adı (String).
8
+ module Types
9
+ module_function
10
+
11
+ def coerce(value, type)
12
+ return value if value.nil?
13
+
14
+ case type
15
+ when String
16
+ klass = Pushizo::Models.const_get(type)
17
+ value.is_a?(Hash) ? klass.new(value) : value
18
+ when Array
19
+ kind, inner = type
20
+ if kind == :array && value.is_a?(Array)
21
+ value.map { |v| coerce(v, inner) }
22
+ elsif kind == :map && value.is_a?(Hash)
23
+ value.each_with_object({}) { |(k, v), h| h[k.to_s] = coerce(v, inner) }
24
+ else
25
+ value
26
+ end
27
+ else
28
+ value
29
+ end
30
+ end
31
+
32
+ # Model, sembol anahtarlı hash ve iç içe yapıları JSON uyumlu (string anahtarlı) değere çevirir.
33
+ def serialize(value)
34
+ case value
35
+ when BaseModel then serialize(value.to_h)
36
+ when Hash then value.each_with_object({}) { |(k, v), h| h[k.to_s] = serialize(v) }
37
+ when Array then value.map { |v| serialize(v) }
38
+ when Symbol then value.to_s
39
+ else value
40
+ end
41
+ end
42
+ end
43
+
44
+ # Hash tabanlı model: alanlara snake_case okuyucu/yazıcı ve [] ile erişilir; bilinmeyen alanlar korunur.
45
+ #
46
+ # msg = hs.notifications.get("msg_1")
47
+ # msg.status # => "completed"
48
+ # msg["status"] # => "completed"
49
+ # msg.to_h # => { "id" => …, "status" => … }
50
+ class BaseModel
51
+ class << self
52
+ # JSON adı => [metot adı, tip]
53
+ def fields
54
+ @fields ||= {}
55
+ end
56
+
57
+ def field(json_name, method_name, type)
58
+ fields[json_name] = [method_name, type]
59
+ define_method(method_name) { Pushizo::Types.coerce(@attributes[json_name], type) }
60
+ define_method(:"#{method_name}=") { |v| @attributes[json_name] = Pushizo::Types.serialize(v) }
61
+ end
62
+
63
+ # JSON adına veya metot adına göre JSON anahtarı.
64
+ def json_key(key)
65
+ name = key.to_s
66
+ return name if fields.key?(name)
67
+
68
+ found = fields.find { |_json, (method, _type)| method.to_s == name }
69
+ found ? found[0] : name
70
+ end
71
+ end
72
+
73
+ def initialize(attributes = {})
74
+ @attributes = {}
75
+ (attributes || {}).each { |k, v| @attributes[self.class.json_key(k)] = Pushizo::Types.serialize(v) }
76
+ end
77
+
78
+ # JSON veya metot adıyla okuma (model alanları model nesnesine çevrilir).
79
+ def [](key)
80
+ json = self.class.json_key(key)
81
+ entry = self.class.fields[json]
82
+ entry ? Pushizo::Types.coerce(@attributes[json], entry[1]) : @attributes[json]
83
+ end
84
+
85
+ def []=(key, value)
86
+ @attributes[self.class.json_key(key)] = Pushizo::Types.serialize(value)
87
+ end
88
+
89
+ def key?(key)
90
+ @attributes.key?(self.class.json_key(key))
91
+ end
92
+
93
+ # JSON adlarıyla ham hash (derin kopya).
94
+ def to_h
95
+ Marshal.load(Marshal.dump(@attributes))
96
+ end
97
+ alias to_hash to_h
98
+
99
+ def to_json(*args)
100
+ @attributes.to_json(*args)
101
+ end
102
+
103
+ def ==(other)
104
+ other.class == self.class && other.to_h == to_h
105
+ end
106
+ alias eql? ==
107
+
108
+ def hash
109
+ [self.class, @attributes].hash
110
+ end
111
+
112
+ def inspect
113
+ "#<#{self.class.name} #{@attributes.inspect}>"
114
+ end
115
+ alias to_s inspect
116
+ end
117
+ end
@@ -1,26 +1,26 @@
1
- # frozen_string_literal: true
2
-
3
- require "net/http"
4
- require "openssl"
5
- require "uri"
6
-
7
- module Pushizo
8
- # Net::HTTP tabanlı varsayılan taşıma. Özel taşıma aynı imzalı #call(method, url, headers, body, timeout)
9
- # metodu sağlar ve [durum, başlıklar (küçük harf anahtar), gövde] döndürür; ağ hatasında istisna fırlatır.
10
- class NetHttpTransport
11
- def call(method, url, headers, body, timeout)
12
- uri = URI.parse(url)
13
- http = Net::HTTP.new(uri.host, uri.port)
14
- http.use_ssl = uri.scheme == "https"
15
- http.open_timeout = timeout
16
- http.read_timeout = timeout
17
- http.write_timeout = timeout if http.respond_to?(:write_timeout=)
18
- request = Net::HTTPGenericRequest.new(method.to_s.upcase, !body.nil?, true, uri.request_uri, headers)
19
- request.body = body unless body.nil?
20
- response = http.start { |conn| conn.request(request) }
21
- response_headers = {}
22
- response.each_header { |k, v| response_headers[k.downcase] = v }
23
- [response.code.to_i, response_headers, response.body.to_s]
24
- end
25
- end
26
- end
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "openssl"
5
+ require "uri"
6
+
7
+ module Pushizo
8
+ # Net::HTTP tabanlı varsayılan taşıma. Özel taşıma aynı imzalı #call(method, url, headers, body, timeout)
9
+ # metodu sağlar ve [durum, başlıklar (küçük harf anahtar), gövde] döndürür; ağ hatasında istisna fırlatır.
10
+ class NetHttpTransport
11
+ def call(method, url, headers, body, timeout)
12
+ uri = URI.parse(url)
13
+ http = Net::HTTP.new(uri.host, uri.port)
14
+ http.use_ssl = uri.scheme == "https"
15
+ http.open_timeout = timeout
16
+ http.read_timeout = timeout
17
+ http.write_timeout = timeout if http.respond_to?(:write_timeout=)
18
+ request = Net::HTTPGenericRequest.new(method.to_s.upcase, !body.nil?, true, uri.request_uri, headers)
19
+ request.body = body unless body.nil?
20
+ response = http.start { |conn| conn.request(request) }
21
+ response_headers = {}
22
+ response.each_header { |k, v| response_headers[k.downcase] = v }
23
+ [response.code.to_i, response_headers, response.body.to_s]
24
+ end
25
+ end
26
+ end
@@ -1,5 +1,5 @@
1
- # frozen_string_literal: true
2
-
3
- module Pushizo
4
- VERSION = "0.1.0"
5
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Pushizo
4
+ VERSION = "0.2.0"
5
+ end
@@ -1,92 +1,92 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
- require "openssl"
5
-
6
- module Pushizo
7
- # Giden webhook imza doğrulaması (alıcı tarafı).
8
- #
9
- # Başlık: Pushizo-Signature: t=<unix>,v1=<hex> — v1 = HMAC-SHA256(secret, "#{t}.#{ham_gövde}").
10
- # Secret rotasyonunda başlıkta iki v1 bulunabilir; herhangi biri eşleşirse imza geçerlidir.
11
- #
12
- # payload = request.body.read # ham gövde (JSON.parse etmeden)
13
- # header = request.get_header("HTTP_PUSHIZO_SIGNATURE")
14
- # events = Pushizo::Webhook.construct_events(payload, header, ENV.fetch("HS_WEBHOOK_SECRET"))
15
- # # Pushizo-Event-Id ile tekrarları ayıklayın; 2xx ile hızlı yanıt verin.
16
- module Webhook
17
- SIGNATURE_HEADER = "Pushizo-Signature"
18
- EVENT_ID_HEADER = "Pushizo-Event-Id"
19
- # Varsayılan zaman damgası toleransı (sn); daha eski t tekrar (replay) sayılır.
20
- DEFAULT_TOLERANCE = 300
21
-
22
- # Doğrulama sonucu: ok? ise timestamp, değilse reason doludur.
23
- Result = Struct.new(:ok, :timestamp, :reason) do
24
- def ok?
25
- ok
26
- end
27
- end
28
-
29
- module_function
30
-
31
- # İmzayı doğrular. payload HAM istek gövdesidir; secret tek değer veya dizi (rotasyon) olabilir.
32
- # now test için Unix zamanıdır (sn). Karşılaştırma sabit zamanlıdır.
33
- def verify_signature(payload, header, secret, tolerance: DEFAULT_TOLERANCE, now: nil)
34
- raw = header.is_a?(Array) ? header.first : header
35
- return Result.new(false, nil, "missing_header") if raw.nil? || raw.to_s.empty?
36
-
37
- timestamp = nil
38
- signatures = []
39
- raw.to_s.split(",").each do |part|
40
- idx = part.index("=")
41
- next if idx.nil? || idx.zero?
42
-
43
- key = part[0...idx].strip
44
- value = part[(idx + 1)..].strip
45
- if key == "t" && value.match?(/\A[0-9]+\z/)
46
- timestamp = value.to_i
47
- elsif key == "v1" && value.match?(/\A[0-9a-f]{64}\z/)
48
- signatures << [value].pack("H*")
49
- end
50
- end
51
- return Result.new(false, nil, "malformed_header") if timestamp.nil? || signatures.empty?
52
-
53
- current = (now || Time.now.to_i).to_i
54
- return Result.new(false, nil, "timestamp_out_of_tolerance") if (current - timestamp).abs > tolerance
55
-
56
- body = payload.to_s.b
57
- Array(secret).each do |s|
58
- expected = OpenSSL::HMAC.digest("SHA256", s.to_s, "#{timestamp}.".b + body)
59
- return Result.new(true, timestamp, nil) if signatures.any? { |sig| secure_compare(sig, expected) }
60
- end
61
- Result.new(false, nil, "signature_mismatch")
62
- end
63
-
64
- # İmzayı doğrular ve gövdeyi olay listesine (Hash dizisi) çevirir: tekil gövde veya {"events": [...]} batch.
65
- # Geçersiz imzada WebhookSignatureError fırlatır.
66
- def construct_events(payload, header, secret, tolerance: DEFAULT_TOLERANCE, now: nil)
67
- result = verify_signature(payload, header, secret, tolerance: tolerance, now: now)
68
- raise WebhookSignatureError, result.reason unless result.ok?
69
-
70
- body = begin
71
- JSON.parse(payload.to_s)
72
- rescue JSON::ParserError
73
- raise WebhookSignatureError, "malformed_body"
74
- end
75
- raise WebhookSignatureError, "malformed_body" unless body.is_a?(Hash)
76
-
77
- body["events"].is_a?(Array) ? body["events"] : [body]
78
- end
79
-
80
- def secure_compare(a, b)
81
- return false unless a.bytesize == b.bytesize
82
-
83
- if OpenSSL.respond_to?(:fixed_length_secure_compare)
84
- OpenSSL.fixed_length_secure_compare(a, b)
85
- else
86
- result = 0
87
- a.bytes.zip(b.bytes) { |x, y| result |= x ^ y }
88
- result.zero?
89
- end
90
- end
91
- end
92
- end
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "openssl"
5
+
6
+ module Pushizo
7
+ # Giden webhook imza doğrulaması (alıcı tarafı).
8
+ #
9
+ # Başlık: Pushizo-Signature: t=<unix>,v1=<hex> — v1 = HMAC-SHA256(secret, "#{t}.#{ham_gövde}").
10
+ # Secret rotasyonunda başlıkta iki v1 bulunabilir; herhangi biri eşleşirse imza geçerlidir.
11
+ #
12
+ # payload = request.body.read # ham gövde (JSON.parse etmeden)
13
+ # header = request.get_header("HTTP_PUSHIZO_SIGNATURE")
14
+ # events = Pushizo::Webhook.construct_events(payload, header, ENV.fetch("HS_WEBHOOK_SECRET"))
15
+ # # Pushizo-Event-Id ile tekrarları ayıklayın; 2xx ile hızlı yanıt verin.
16
+ module Webhook
17
+ SIGNATURE_HEADER = "Pushizo-Signature"
18
+ EVENT_ID_HEADER = "Pushizo-Event-Id"
19
+ # Varsayılan zaman damgası toleransı (sn); daha eski t tekrar (replay) sayılır.
20
+ DEFAULT_TOLERANCE = 300
21
+
22
+ # Doğrulama sonucu: ok? ise timestamp, değilse reason doludur.
23
+ Result = Struct.new(:ok, :timestamp, :reason) do
24
+ def ok?
25
+ ok
26
+ end
27
+ end
28
+
29
+ module_function
30
+
31
+ # İmzayı doğrular. payload HAM istek gövdesidir; secret tek değer veya dizi (rotasyon) olabilir.
32
+ # now test için Unix zamanıdır (sn). Karşılaştırma sabit zamanlıdır.
33
+ def verify_signature(payload, header, secret, tolerance: DEFAULT_TOLERANCE, now: nil)
34
+ raw = header.is_a?(Array) ? header.first : header
35
+ return Result.new(false, nil, "missing_header") if raw.nil? || raw.to_s.empty?
36
+
37
+ timestamp = nil
38
+ signatures = []
39
+ raw.to_s.split(",").each do |part|
40
+ idx = part.index("=")
41
+ next if idx.nil? || idx.zero?
42
+
43
+ key = part[0...idx].strip
44
+ value = part[(idx + 1)..].strip
45
+ if key == "t" && value.match?(/\A[0-9]+\z/)
46
+ timestamp = value.to_i
47
+ elsif key == "v1" && value.match?(/\A[0-9a-f]{64}\z/)
48
+ signatures << [value].pack("H*")
49
+ end
50
+ end
51
+ return Result.new(false, nil, "malformed_header") if timestamp.nil? || signatures.empty?
52
+
53
+ current = (now || Time.now.to_i).to_i
54
+ return Result.new(false, nil, "timestamp_out_of_tolerance") if (current - timestamp).abs > tolerance
55
+
56
+ body = payload.to_s.b
57
+ Array(secret).each do |s|
58
+ expected = OpenSSL::HMAC.digest("SHA256", s.to_s, "#{timestamp}.".b + body)
59
+ return Result.new(true, timestamp, nil) if signatures.any? { |sig| secure_compare(sig, expected) }
60
+ end
61
+ Result.new(false, nil, "signature_mismatch")
62
+ end
63
+
64
+ # İmzayı doğrular ve gövdeyi olay listesine (Hash dizisi) çevirir: tekil gövde veya {"events": [...]} batch.
65
+ # Geçersiz imzada WebhookSignatureError fırlatır.
66
+ def construct_events(payload, header, secret, tolerance: DEFAULT_TOLERANCE, now: nil)
67
+ result = verify_signature(payload, header, secret, tolerance: tolerance, now: now)
68
+ raise WebhookSignatureError, result.reason unless result.ok?
69
+
70
+ body = begin
71
+ JSON.parse(payload.to_s)
72
+ rescue JSON::ParserError
73
+ raise WebhookSignatureError, "malformed_body"
74
+ end
75
+ raise WebhookSignatureError, "malformed_body" unless body.is_a?(Hash)
76
+
77
+ body["events"].is_a?(Array) ? body["events"] : [body]
78
+ end
79
+
80
+ def secure_compare(a, b)
81
+ return false unless a.bytesize == b.bytesize
82
+
83
+ if OpenSSL.respond_to?(:fixed_length_secure_compare)
84
+ OpenSSL.fixed_length_secure_compare(a, b)
85
+ else
86
+ result = 0
87
+ a.bytes.zip(b.bytes) { |x, y| result |= x ^ y }
88
+ result.zero?
89
+ end
90
+ end
91
+ end
92
+ end
data/lib/pushizo.rb CHANGED
@@ -1,27 +1,27 @@
1
- # frozen_string_literal: true
2
-
3
- # Pushizo Ruby sunucu SDK'sı (Net::HTTP, bağımlılıksız).
4
- #
5
- # hs = Pushizo::Client.new(api_key: ENV.fetch("HS_API_KEY"), app_id: "app_…")
6
- # hs.notifications.create(
7
- # { target: { include_aliases: { external_id: [user_id] } }, contents: { en: "Shipped" } },
8
- # idempotency_key: "order-#{order_id}"
9
- # )
10
- require_relative "pushizo/version"
11
- require_relative "pushizo/errors"
12
- require_relative "pushizo/model"
13
- require_relative "pushizo/file_upload"
14
- require_relative "pushizo/generated/models"
15
- require_relative "pushizo/base_resource"
16
- require_relative "pushizo/generated/resources"
17
- require_relative "pushizo/transport"
18
- require_relative "pushizo/client"
19
- require_relative "pushizo/webhook"
20
- require_relative "pushizo/identity_token"
21
-
22
- module Pushizo
23
- # SDK identity verification için identity_hash = HMAC-SHA256(sdk_secret, external_id) (hex).
24
- def self.identity_hash(sdk_secret, external_id)
25
- OpenSSL::HMAC.hexdigest("SHA256", sdk_secret, external_id.to_s)
26
- end
27
- end
1
+ # frozen_string_literal: true
2
+
3
+ # Pushizo Ruby sunucu SDK'sı (Net::HTTP, bağımlılıksız).
4
+ #
5
+ # hs = Pushizo::Client.new(api_key: ENV.fetch("HS_API_KEY"), app_id: "app_…")
6
+ # hs.notifications.create(
7
+ # { target: { include_aliases: { external_id: [user_id] } }, contents: { en: "Shipped" } },
8
+ # idempotency_key: "order-#{order_id}"
9
+ # )
10
+ require_relative "pushizo/version"
11
+ require_relative "pushizo/errors"
12
+ require_relative "pushizo/model"
13
+ require_relative "pushizo/file_upload"
14
+ require_relative "pushizo/generated/models"
15
+ require_relative "pushizo/base_resource"
16
+ require_relative "pushizo/generated/resources"
17
+ require_relative "pushizo/transport"
18
+ require_relative "pushizo/client"
19
+ require_relative "pushizo/webhook"
20
+ require_relative "pushizo/identity_token"
21
+
22
+ module Pushizo
23
+ # SDK identity verification için identity_hash = HMAC-SHA256(sdk_secret, external_id) (hex).
24
+ def self.identity_hash(sdk_secret, external_id)
25
+ OpenSSL::HMAC.hexdigest("SHA256", sdk_secret, external_id.to_s)
26
+ end
27
+ end
metadata CHANGED
@@ -1,18 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pushizo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pushizo
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-10-22 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies: []
13
12
  description: 'Pushizo public API istemcisi: bildirim gönderme, kullanıcı/abonelik
14
13
  yönetimi, segment/şablon/kampanya ve webhook imza doğrulaması (Net::HTTP, bağımlılıksız).'
15
- email:
16
14
  executables: []
17
15
  extensions: []
18
16
  extra_rdoc_files: []
@@ -40,7 +38,6 @@ metadata:
40
38
  documentation_uri: https://docs.pushizo.com/docs/sdk/ruby
41
39
  source_code_uri: https://github.com/pushizo/pushizo-ruby
42
40
  bug_tracker_uri: https://github.com/pushizo/pushizo-ruby/issues
43
- post_install_message:
44
41
  rdoc_options: []
45
42
  require_paths:
46
43
  - lib
@@ -55,8 +52,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
55
52
  - !ruby/object:Gem::Version
56
53
  version: '0'
57
54
  requirements: []
58
- rubygems_version: 3.5.22
59
- signing_key:
55
+ rubygems_version: 4.0.20
60
56
  specification_version: 4
61
57
  summary: Pushizo sunucu SDK'sı
62
58
  test_files: []